Move iTip message parsing functionality to libcalendaring. Only parse iCal attachments once although used by calendar and tasks
This commit is contained in:
parent
cec5f19cb4
commit
978c9023e5
3 changed files with 189 additions and 232 deletions
|
@ -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)
|
||||
|
|
|
@ -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 *********/
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue