Move iTip message parsing functionality to libcalendaring. Only parse iCal attachments once although used by calendar and tasks

This commit is contained in:
Thomas Bruederli 2014-07-31 18:21:53 +02:00
parent cec5f19cb4
commit 978c9023e5
3 changed files with 189 additions and 232 deletions

View file

@ -173,7 +173,6 @@ class calendar extends rcube_plugin
else if ($args['task'] == 'mail') {
// hooks to catch event invitations on incoming mails
if ($args['action'] == 'show' || $args['action'] == 'preview') {
$this->add_hook('message_load', array($this, 'mail_message_load'));
$this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
}
@ -2287,7 +2286,7 @@ class calendar extends rcube_plugin
foreach ($p['messages'] as $i => $header) {
$part = new StdClass;
$part->mimetype = $header->ctype;
if ($this->is_vcalendar($part)) {
if (libcalendaring::part_is_vcalendar($part)) {
$header->list_flags['attachmentClass'] = 'ical';
}
else if (in_array($header->ctype, array('multipart/alternative', 'multipart/mixed'))) {
@ -2295,7 +2294,7 @@ class calendar extends rcube_plugin
if (!empty($header->structure) && is_array($header->structure->parts)) {
foreach ($header->structure->parts as $part) {
if ($this->is_vcalendar($part) && !empty($part->ctype_parameters['method'])) {
if (libcalendaring::part_is_vcalendar($part) && !empty($part->ctype_parameters['method'])) {
$header->list_flags['attachmentClass'] = 'ical';
break;
}
@ -2305,29 +2304,6 @@ class calendar extends rcube_plugin
}
}
}
/**
* Check mail message structure of there are .ics files attached
*/
public function mail_message_load($p)
{
$this->message = $p['object'];
$itip_part = null;
// check all message parts for .ics files
foreach ((array)$this->message->mime_parts as $part) {
if ($this->is_vcalendar($part)) {
if ($part->ctype_parameters['method'])
$itip_part = $part->mime_id;
else
$this->ics_parts[] = $part->mime_id;
}
}
// priorize part with method parameter
if ($itip_part)
$this->ics_parts = array($itip_part);
}
/**
* Add UI element to copy event invitations or updates to the calendar
@ -2335,47 +2311,38 @@ class calendar extends rcube_plugin
public function mail_messagebody_html($p)
{
// load iCalendar functions (if necessary)
if (!empty($this->ics_parts)) {
if (!empty($this->lib->ical_parts)) {
$this->get_ical();
$this->load_itip();
}
$html = '';
$has_events = false;
foreach ($this->ics_parts as $mime_id) {
$part = $this->message->mime_parts[$mime_id];
$charset = $part->ctype_parameters['charset'] ? $part->ctype_parameters['charset'] : RCMAIL_CHARSET;
$events = $this->ical->import($this->message->get_part_content($mime_id), $charset);
$title = $this->gettext('title');
$ical_objects = $this->lib->get_mail_ical_objects();
// successfully parsed events?
if (empty($events))
continue;
// show a box for every event in the file
foreach ($ical_objects as $idx => $event) {
if ($event['_type'] != 'event') // skip non-event objects (#2928)
continue;
// show a box for every event in the file
foreach ($events as $idx => $event) {
if ($event['_type'] != 'event') // skip non-event objects (#2928)
continue;
$has_events = true;
$has_events = true;
// get prepared inline UI for this event object
if ($this->ical->method) {
$html .= html::div('calendar-invitebox',
$this->itip->mail_itip_inline_ui(
$event,
$this->ical->method,
$mime_id.':'.$idx,
'calendar',
rcube_utils::anytodatetime($this->message->headers->date)
)
);
}
// limit listing
if ($idx >= 3)
break;
// get prepared inline UI for this event object
if ($ical_objects->method) {
$html .= html::div('calendar-invitebox',
$this->itip->mail_itip_inline_ui(
$event,
$ical_objects->method,
$ical_objects->mime_id . ':' . $idx,
'calendar',
rcube_utils::anytodatetime($ical_objects->message_date)
)
);
}
// limit listing
if ($idx >= 3)
break;
}
// prepend event boxes to message body
@ -2403,40 +2370,6 @@ class calendar extends rcube_plugin
return $p;
}
/**
* Read the given mime message from IMAP and parse ical data
*/
private function mail_get_itip_event($mbox, $uid, $mime_id)
{
$charset = RCMAIL_CHARSET;
// establish imap connection
$imap = $this->rc->get_storage();
$imap->set_mailbox($mbox);
if ($uid && $mime_id) {
list($mime_id, $index) = explode(':', $mime_id);
$part = $imap->get_message_part($uid, $mime_id);
if ($part->ctype_parameters['charset'])
$charset = $part->ctype_parameters['charset'];
$headers = $imap->get_message_headers($uid);
if ($part) {
$events = $this->get_ical()->import($part, $charset);
}
}
// successfully parsed events?
if (!empty($events) && ($event = $events[$index])) {
// store the message's sender address for comparisons
$event['_sender'] = preg_match('/([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))/', $headers->from, $m) ? $m[1] : '';
$event['_sender_utf'] = rcube_idn_to_utf8($event['_sender']);
return $event;
}
return null;
}
/**
* Handler for POST request to import an event attached to a mail message
@ -2454,7 +2387,7 @@ class calendar extends rcube_plugin
$success = false;
// successfully parsed events?
if ($event = $this->mail_get_itip_event($mbox, $uid, $mime_id)) {
if ($event = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event')) {
// find writeable calendar to store event
$cal_id = !empty($_REQUEST['_folder']) ? get_input_value('_folder', RCUBE_INPUT_POST) : null;
$calendars = $this->driver->list_calendars(false, true);
@ -2635,7 +2568,7 @@ class calendar extends rcube_plugin
$mbox = get_input_value('_mbox', RCUBE_INPUT_POST);
$mime_id = get_input_value('_part', RCUBE_INPUT_POST);
if (($event = $this->mail_get_itip_event($mbox, $uid, $mime_id)) && $this->ical->method == 'REPLY') {
if (($event = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event')) && $event['_method'] == 'REPLY') {
$event['comment'] = get_input_value('_comment', RCUBE_INPUT_POST);
foreach ($event['attendees'] as $_attendee) {
@ -2807,21 +2740,6 @@ class calendar extends rcube_plugin
return $args;
}
/**
* 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 (
in_array($part->mimetype, array('text/calendar', 'text/x-vcalendar', 'application/ics')) ||
// Apple sends files as application/x-any (!?)
($part->mimetype == 'application/x-any' && $part->filename && preg_match('/\.ics$/i', $part->filename))
);
}
/**
* Get a list of email addresses of the current user (from login and identities)

View file

@ -35,6 +35,8 @@ class libcalendaring extends rcube_plugin
public $gmt_offset;
public $dst_active;
public $timezone_offset;
public $ical_parts = array();
public $ical_message;
public $defaults = array(
'calendar_date_format' => "yyyy-MM-dd",
@ -57,6 +59,8 @@ class libcalendaring extends rcube_plugin
private static $instance;
private $mail_ical_parser;
/**
* Singleton getter to allow direct access from other plugins
*/
@ -102,6 +106,21 @@ class libcalendaring extends rcube_plugin
$this->add_hook('refresh', array($this, 'refresh'));
$this->register_action('plugin.alarms', array($this, 'alarms_action'));
}
// proceed initialization in startup hook
$this->add_hook('startup', array($this, 'startup'));
}
/**
* Startup hook
*/
public function startup($args)
{
if ($args['task'] == 'mail') {
if ($args['action'] == 'show' || $args['action'] == 'preview') {
$this->add_hook('message_load', array($this, 'mail_message_load'));
}
}
}
/**
@ -1220,6 +1239,125 @@ class libcalendaring extends rcube_plugin
}
/********* iTip message detection *********/
/**
* Check mail message structure of there are .ics files attached
*/
public function mail_message_load($p)
{
$this->ical_message = $p['object'];
$itip_part = null;
// check all message parts for .ics files
foreach ((array)$this->ical_message->mime_parts as $part) {
if (self::part_is_vcalendar($part)) {
if ($part->ctype_parameters['method'])
$itip_part = $part->mime_id;
else
$this->ical_parts[] = $part->mime_id;
}
}
// priorize part with method parameter
if ($itip_part) {
$this->ical_parts = array($itip_part);
}
}
/**
* Getter for the parsed iCal objects attached to the current email message
*
* @return object libvcalendar parser instance with the parsed objects
*/
public function get_mail_ical_objects()
{
// create parser and load ical objects
if (!$this->mail_ical_parser) {
$this->mail_ical_parser = $this->get_ical();
foreach ($this->ical_parts as $mime_id) {
$part = $this->ical_message->mime_parts[$mime_id];
$charset = $part->ctype_parameters['charset'] ?: RCMAIL_CHARSET;
$this->mail_ical_parser->import($this->ical_message->get_part_content($mime_id), $charset);
// stop on the part that has an iTip method specified
if (count($this->mail_ical_parser->objects) && $this->mail_ical_parser->method) {
$this->mail_ical_parser->message_date = $this->ical_message->headers->date;
$this->mail_ical_parser->mime_id = $mime_id;
break;
}
}
}
return $this->mail_ical_parser;
}
/**
* Read the given mime message from IMAP and parse ical data
*
* @param string Mailbox name
* @param string Message UID
* @param string Message part ID and object index (e.g. '1.2:0')
* @param string Object type filter (optional)
*
* @return array Hash array with the parsed iCal
*/
public function mail_get_itip_object($mbox, $uid, $mime_id, $type = null)
{
$charset = RCMAIL_CHARSET;
// establish imap connection
$imap = $this->rc->get_storage();
$imap->set_mailbox($mbox);
if ($uid && $mime_id) {
list($mime_id, $index) = explode(':', $mime_id);
$part = $imap->get_message_part($uid, $mime_id);
$headers = $imap->get_message_headers($uid);
$parser = $this->get_ical();
if ($part->ctype_parameters['charset']) {
$charset = $part->ctype_parameters['charset'];
}
if ($part) {
$objects = $parser->import($part, $charset);
}
}
// successfully parsed events/tasks?
if (!empty($objects) && ($object = $objects[$index]) && (!$type || $object['_type'] == $type)) {
if ($parser->method)
$object['_method'] = $parser->method;
// store the message's sender address for comparisons
$object['_sender'] = preg_match('/([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))/', $headers->from, $m) ? $m[1] : '';
$object['_sender_utf'] = rcube_idn_to_utf8($object['_sender']);
return $object;
}
return null;
}
/**
* Checks if specified message part is a vcalendar data
*
* @param rcube_message_part Part object
* @return boolean True if part is of type vcard
*/
public static function part_is_vcalendar($part)
{
return (
in_array($part->mimetype, array('text/calendar', 'text/x-vcalendar', 'application/ics')) ||
// Apple sends files as application/x-any (!?)
($part->mimetype == 'application/x-any' && $part->filename && preg_match('/\.ics$/i', $part->filename))
);
}
/********* Static utility functions *********/
/**

View file

@ -123,7 +123,6 @@ class tasklist extends rcube_plugin
}
else if ($args['task'] == 'mail') {
if ($args['action'] == 'show' || $args['action'] == 'preview') {
$this->add_hook('message_load', array($this, 'mail_message_load'));
$this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
}
@ -1337,84 +1336,44 @@ class tasklist extends rcube_plugin
}
/**
* Check mail message structure of there are .ics files attached
*
* @todo move to libcalendaring
*/
public function mail_message_load($p)
{
$this->message = $p['object'];
$itip_part = null;
// check all message parts for .ics files
foreach ((array)$this->message->mime_parts as $part) {
if ($this->is_vcalendar($part)) {
if ($part->ctype_parameters['method'])
$itip_part = $part->mime_id;
else
$this->ics_parts[] = $part->mime_id;
}
}
// priorize part with method parameter
if ($itip_part) {
$this->ics_parts = array($itip_part);
}
}
/**
* Add UI element to copy event invitations or updates to the calendar
*
* @todo move to libcalendaring
* Add UI element to copy task invitations or updates to the tasklist
*/
public function mail_messagebody_html($p)
{
// load iCalendar functions (if necessary)
if (!empty($this->ics_parts)) {
if (!empty($this->lib->ical_parts)) {
$this->get_ical();
$this->load_itip();
}
// @todo: Calendar plugin does the same, which means the
// attachment body is fetched twice, this is not optimal
$html = '';
$has_tasks = false;
foreach ($this->ics_parts as $mime_id) {
$part = $this->message->mime_parts[$mime_id];
$charset = $part->ctype_parameters['charset'] ? $part->ctype_parameters['charset'] : RCMAIL_CHARSET;
$objects = $this->ical->import($this->message->get_part_content($mime_id), $charset);
$title = $this->gettext('title');
$ical_objects = $this->lib->get_mail_ical_objects();
// successfully parsed events?
if (empty($objects)) {
// show a box for every task in the file
foreach ($ical_objects as $idx => $task) {
if ($task['_type'] != 'task') {
continue;
}
// show a box for every task in the file
foreach ($objects as $idx => $task) {
if ($task['_type'] != 'task') {
continue;
}
$has_tasks = true;
$has_tasks = true;
// get prepared inline UI for this event object
if ($ical_objects->method) {
$html .= html::div('tasklist-invitebox',
$this->itip->mail_itip_inline_ui(
$task,
$ical_objects->method,
$ical_objects->mime_id . ':' . $idx,
'tasks',
rcube_utils::anytodatetime($ical_objects->message_date)
)
);
}
// get prepared inline UI for this event object
if ($this->ical->method) {
$html .= html::div('tasklist-invitebox',
$this->itip->mail_itip_inline_ui(
$task,
$this->ical->method,
$mime_id . ':' . $idx,
'tasks',
rcube_utils::anytodatetime($this->message->headers->date)
)
);
}
// limit listing
if ($idx >= 3) {
break;
}
// limit listing
if ($idx >= 3) {
break;
}
}
@ -1446,66 +1405,6 @@ class tasklist extends rcube_plugin
return $p;
}
/**
* Read the given mime message from IMAP and parse ical data
*
* @todo move to libcalendaring
*/
private function mail_get_itip_task($mbox, $uid, $mime_id)
{
$charset = RCMAIL_CHARSET;
// establish imap connection
$imap = $this->rc->get_storage();
$imap->set_mailbox($mbox);
if ($uid && $mime_id) {
list($mime_id, $index) = explode(':', $mime_id);
$part = $imap->get_message_part($uid, $mime_id);
$headers = $imap->get_message_headers($uid);
if ($part->ctype_parameters['charset']) {
$charset = $part->ctype_parameters['charset'];
}
if ($part) {
$tasks = $this->get_ical()->import($part, $charset);
}
}
// successfully parsed events?
if (!empty($tasks) && ($task = $tasks[$index])) {
$task = $this->from_ical($task);
// store the message's sender address for comparisons
$task['_sender'] = preg_match('/([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))/', $headers->from, $m) ? $m[1] : '';
$askt['_sender_utf'] = rcube_idn_to_utf8($task['_sender']);
return $task;
}
return null;
}
/**
* Checks if specified message part is a vcalendar data
*
* @param rcube_message_part Part object
*
* @return boolean True if part is of type vcard
*
* @todo move to libcalendaring
*/
private function is_vcalendar($part)
{
return (
in_array($part->mimetype, array('text/calendar', 'text/x-vcalendar', 'application/ics')) ||
// Apple sends files as application/x-any (!?)
($part->mimetype == 'application/x-any' && $part->filename && preg_match('/\.ics$/i', $part->filename))
);
}
/**
* Load iCalendar functions
*/
@ -1624,7 +1523,9 @@ class tasklist extends rcube_plugin
$success = false;
// successfully parsed tasks?
if ($task = $this->mail_get_itip_task($mbox, $uid, $mime_id)) {
if ($task = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'task')) {
$task = $this->from_ical($task);
// find writeable list to store the task
$list_id = !empty($_REQUEST['_list']) ? rcube_utils::get_input_value('_list', rcube_utils::INPUT_POST) : null;
$lists = $this->driver->get_lists();