Added iTip handling in tasklist plugin (code copied from Calendar)
This commit is contained in:
parent
f2c6a3851d
commit
445edd09b7
7 changed files with 672 additions and 10 deletions
|
@ -77,7 +77,7 @@ $labels['at'] = 'at';
|
||||||
$labels['this'] = 'this';
|
$labels['this'] = 'this';
|
||||||
$labels['next'] = 'next';
|
$labels['next'] = 'next';
|
||||||
|
|
||||||
// mesages
|
// messages
|
||||||
$labels['savingdata'] = 'Saving data...';
|
$labels['savingdata'] = 'Saving data...';
|
||||||
$labels['errorsaving'] = 'Failed to save data.';
|
$labels['errorsaving'] = 'Failed to save data.';
|
||||||
$labels['notasksfound'] = 'No tasks found for the given criteria';
|
$labels['notasksfound'] = 'No tasks found for the given criteria';
|
||||||
|
@ -138,3 +138,17 @@ $labels['errornotifying'] = 'Failed to send notifications to task participants';
|
||||||
$labels['andnmore'] = '$nr more...';
|
$labels['andnmore'] = '$nr more...';
|
||||||
$labels['delegatedto'] = 'Delegated to: ';
|
$labels['delegatedto'] = 'Delegated to: ';
|
||||||
$labels['delegatedfrom'] = 'Delegated from: ';
|
$labels['delegatedfrom'] = 'Delegated from: ';
|
||||||
|
$labels['savetotasklist'] = 'Save to tasks';
|
||||||
|
$labels['comment'] = 'Comment';
|
||||||
|
$labels['errorimportingtask'] = 'Failed to import task(s)';
|
||||||
|
$labels['importwarningexists'] = 'A copy of this task already exists in your tasklist.';
|
||||||
|
$labels['importsuccess'] = 'Successfully imported $nr tasks';
|
||||||
|
$labels['newerversionexists'] = 'A newer version of this task already exists! Aborted.';
|
||||||
|
$labels['nowritetasklistfound'] = 'No tasklist found to save the task';
|
||||||
|
$labels['importedsuccessfully'] = 'The task was successfully added to \'$list\'';
|
||||||
|
$labels['updatedsuccessfully'] = 'The task was successfully updated in \'$list\'';
|
||||||
|
$labels['attendeupdateesuccess'] = 'Successfully updated the participant\'s status';
|
||||||
|
$labels['itipresponseerror'] = 'Failed to send the response to this task invitation';
|
||||||
|
$labels['itipinvalidrequest'] = 'This invitation is no longer valid';
|
||||||
|
$labels['sentresponseto'] = 'Successfully sent invitation response to $mailto';
|
||||||
|
$labels['successremoval'] = 'The task has been deleted successfully.';
|
||||||
|
|
BIN
plugins/tasklist/skins/larry/images/ical-attachment.png
Normal file
BIN
plugins/tasklist/skins/larry/images/ical-attachment.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 620 B |
BIN
plugins/tasklist/skins/larry/images/loading_blue.gif
Normal file
BIN
plugins/tasklist/skins/larry/images/loading_blue.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 847 B |
BIN
plugins/tasklist/skins/larry/images/tasklist.png
Normal file
BIN
plugins/tasklist/skins/larry/images/tasklist.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -20,7 +20,8 @@
|
||||||
background-position: 0 -26px;
|
background-position: 0 -26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.toolbarmenu li span.icon.taskadd {
|
ul.toolbarmenu li span.icon.taskadd,
|
||||||
|
#attachmentmenu li a.tasklistlink span.icon.taskadd {
|
||||||
background-image: url(buttons.png);
|
background-image: url(buttons.png);
|
||||||
background-position: -4px -90px;
|
background-position: -4px -90px;
|
||||||
}
|
}
|
||||||
|
@ -1085,6 +1086,98 @@ label.block {
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Invitation UI in mail */
|
||||||
|
|
||||||
|
.messagelist tbody .attachment span.ical {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 18px;
|
||||||
|
width: 20px;
|
||||||
|
padding: 0;
|
||||||
|
background: url(images/ical-attachment.png) 2px 1px no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.tasklist-invitebox {
|
||||||
|
min-height: 20px;
|
||||||
|
margin: 5px 8px;
|
||||||
|
padding: 3px 6px 6px 34px;
|
||||||
|
border: 1px solid #ffdf0e;
|
||||||
|
background: url(images/tasklist.png) 6px 5px no-repeat #fef893;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.tasklist-invitebox td.ititle {
|
||||||
|
font-weight: bold;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.tasklist-invitebox td.label {
|
||||||
|
color: #666;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#event-rsvp .rsvp-buttons,
|
||||||
|
div.tasklist-invitebox .itip-buttons div {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#event-rsvp input.button,
|
||||||
|
div.tasklist-invitebox input.button {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.tasklist-invitebox .folder-select {
|
||||||
|
font-weight: 10px;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.tasklist-invitebox .rsvp-status {
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.tasklist-invitebox .rsvp-status.loading {
|
||||||
|
color: #666;
|
||||||
|
padding: 1px 0 2px 24px;
|
||||||
|
background: url(images/loading_blue.gif) top left no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.tasklist-invitebox .rsvp-status.hint {
|
||||||
|
color: #666;
|
||||||
|
text-shadow: none;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
#event-partstat .changersvp,
|
||||||
|
div.tasklist-invitebox .rsvp-status.declined,
|
||||||
|
div.tasklist-invitebox .rsvp-status.tentative,
|
||||||
|
div.tasklist-invitebox .rsvp-status.accepted,
|
||||||
|
div.tasklist-invitebox .rsvp-status.delegated,
|
||||||
|
div.tasklist-invitebox .rsvp-status.needs-action {
|
||||||
|
padding: 0 0 1px 22px;
|
||||||
|
background: url(images/attendee-status.png) 2px -20px no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#event-partstat .changersvp.declined,
|
||||||
|
div.tasklist-invitebox .rsvp-status.declined {
|
||||||
|
background-position: 2px -40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#event-partstat .changersvp.tentative,
|
||||||
|
div.tasklist-invitebox .rsvp-status.tentative {
|
||||||
|
background-position: 2px -60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#event-partstat .changersvp.delegated,
|
||||||
|
div.tasklist-invitebox .rsvp-status.delegated {
|
||||||
|
background-position: 2px -180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#event-partstat .changersvp.needs-action,
|
||||||
|
div.tasklist-invitebox .rsvp-status.needs-action {
|
||||||
|
background-position: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Special hacks for IE7 **/
|
/** Special hacks for IE7 **/
|
||||||
/** They need to be in this file to also affect the task-create dialog embedded in mail view **/
|
/** They need to be in this file to also affect the task-create dialog embedded in mail view **/
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ class tasklist extends rcube_plugin
|
||||||
|
|
||||||
private $collapsed_tasks = array();
|
private $collapsed_tasks = array();
|
||||||
private $itip;
|
private $itip;
|
||||||
|
private $ical;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,15 +108,19 @@ class tasklist extends rcube_plugin
|
||||||
$this->register_action('mail2task', array($this, 'mail_message2task'));
|
$this->register_action('mail2task', array($this, 'mail_message2task'));
|
||||||
$this->register_action('get-attachment', array($this, 'attachment_get'));
|
$this->register_action('get-attachment', array($this, 'attachment_get'));
|
||||||
$this->register_action('upload', array($this, 'attachment_upload'));
|
$this->register_action('upload', array($this, 'attachment_upload'));
|
||||||
|
$this->register_action('mailimportitip', array($this, 'mail_import_itip'));
|
||||||
|
$this->register_action('mailimportattach', array($this, 'mail_import_attachment'));
|
||||||
|
$this->register_action('itip-status', array($this, 'task_itip_status'));
|
||||||
|
$this->register_action('itip-remove', array($this, 'task_itip_remove'));
|
||||||
|
$this->register_action('itip-decline-reply', array($this, 'mail_itip_decline_reply'));
|
||||||
$this->add_hook('refresh', array($this, 'refresh'));
|
$this->add_hook('refresh', array($this, 'refresh'));
|
||||||
|
|
||||||
$this->collapsed_tasks = array_filter(explode(',', $this->rc->config->get('tasklist_collapsed_tasks', '')));
|
$this->collapsed_tasks = array_filter(explode(',', $this->rc->config->get('tasklist_collapsed_tasks', '')));
|
||||||
}
|
}
|
||||||
else if ($args['task'] == 'mail') {
|
else if ($args['task'] == 'mail') {
|
||||||
// TODO: register hooks to catch ical/vtodo email attachments
|
|
||||||
if ($args['action'] == 'show' || $args['action'] == 'preview') {
|
if ($args['action'] == 'show' || $args['action'] == 'preview') {
|
||||||
// $this->add_hook('message_load', array($this, 'mail_message_load'));
|
$this->add_hook('message_load', array($this, 'mail_message_load'));
|
||||||
// $this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
|
$this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// add 'Create event' item to message menu
|
// add 'Create event' item to message menu
|
||||||
|
@ -1255,6 +1260,532 @@ class tasklist extends rcube_plugin
|
||||||
$this->rc->output->send();
|
$this->rc->output->send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
public function mail_messagebody_html($p)
|
||||||
|
{
|
||||||
|
// load iCalendar functions (if necessary)
|
||||||
|
if (!empty($this->ics_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 = '';
|
||||||
|
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');
|
||||||
|
|
||||||
|
// successfully parsed events?
|
||||||
|
if (empty($objects)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// show a box for every task in the file
|
||||||
|
foreach ($objects as $idx => $task) {
|
||||||
|
if ($task['_type'] != 'task') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get prepared inline UI for this event object
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepend event boxes to message body
|
||||||
|
if ($html) {
|
||||||
|
$this->load_ui();
|
||||||
|
$this->ui->init();
|
||||||
|
|
||||||
|
$p['content'] = $html . $p['content'];
|
||||||
|
|
||||||
|
$this->rc->output->add_label('tasklist.savingdata','tasklist.deletetaskconfirm','tasklist.declinedeleteconfirm');
|
||||||
|
|
||||||
|
// add "Save to calendar" button into attachment menu
|
||||||
|
$this->add_button(array(
|
||||||
|
'id' => 'attachmentsavetask',
|
||||||
|
'name' => 'attachmentsavetask',
|
||||||
|
'type' => 'link',
|
||||||
|
'wrapper' => 'li',
|
||||||
|
'command' => 'attachment-save-task',
|
||||||
|
'class' => 'icon tasklistlink',
|
||||||
|
'classact' => 'icon tasklistlink active',
|
||||||
|
'innerclass' => 'icon taskadd',
|
||||||
|
'label' => 'tasklist.savetotasklist',
|
||||||
|
), 'attachmentmenu');
|
||||||
|
}
|
||||||
|
|
||||||
|
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])) {
|
||||||
|
// 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
|
||||||
|
*/
|
||||||
|
public function get_ical()
|
||||||
|
{
|
||||||
|
if (!$this->ical) {
|
||||||
|
$this->ical = libcalendaring::get_ical();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get properties of the tasklist this user has specified as default
|
||||||
|
*/
|
||||||
|
public function get_default_tasklist($writeable = false)
|
||||||
|
{
|
||||||
|
// $default_id = $this->rc->config->get('tasklist_default_list');
|
||||||
|
$lists = $this->driver->get_lists();
|
||||||
|
// $list = $calendars[$default_id] ?: null;
|
||||||
|
|
||||||
|
if (!$list || ($writeable && !$list['editable'])) {
|
||||||
|
foreach ($lists as $l) {
|
||||||
|
if ($l['default']) {
|
||||||
|
$list = $l;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$writeable || $l['editable']) {
|
||||||
|
$first = $l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list ?: $first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import the full payload from a mail message attachment
|
||||||
|
*/
|
||||||
|
public function mail_import_attachment()
|
||||||
|
{
|
||||||
|
$uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
|
||||||
|
$mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
|
||||||
|
$mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST);
|
||||||
|
$charset = RCMAIL_CHARSET;
|
||||||
|
|
||||||
|
// establish imap connection
|
||||||
|
$imap = $this->rc->get_storage();
|
||||||
|
$imap->set_mailbox($mbox);
|
||||||
|
|
||||||
|
if ($uid && $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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = $existing = 0;
|
||||||
|
|
||||||
|
if (!empty($tasks)) {
|
||||||
|
// find writeable tasklist to store task
|
||||||
|
$cal_id = !empty($_REQUEST['_list']) ? rcube_utils::get_input_value('_list', rcube_utils::INPUT_POST) : null;
|
||||||
|
$lists = $this->driver->get_lists();
|
||||||
|
$list = $lists[$cal_id] ?: $this->get_default_tasklist(true);
|
||||||
|
|
||||||
|
foreach ($tasks as $task) {
|
||||||
|
// save to tasklist
|
||||||
|
if ($list && $list['editable'] && $task['_type'] == 'task') {
|
||||||
|
$task['list'] = $list['id'];
|
||||||
|
|
||||||
|
if (!$this->driver->get_task($task['uid'])) {
|
||||||
|
$success += (bool) $this->driver->create_task($task);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$existing++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
$this->rc->output->command('display_message', $this->gettext(array(
|
||||||
|
'name' => 'importsuccess',
|
||||||
|
'vars' => array('nr' => $success),
|
||||||
|
)), 'confirmation');
|
||||||
|
}
|
||||||
|
else if ($existing) {
|
||||||
|
$this->rc->output->command('display_message', $this->gettext('importwarningexists'), 'warning');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->rc->output->command('display_message', $this->gettext('errorimportingtask'), 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for POST request to import an event attached to a mail message
|
||||||
|
*/
|
||||||
|
public function mail_import_itip()
|
||||||
|
{
|
||||||
|
$uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
|
||||||
|
$mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
|
||||||
|
$mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST);
|
||||||
|
$status = rcube_utils::get_input_value('_status', rcube_utils::INPUT_POST);
|
||||||
|
$delete = intval(rcube_utils::get_input_value('_del', rcube_utils::INPUT_POST));
|
||||||
|
$noreply = intval(rcube_utils::get_input_value('_noreply', rcube_utils::INPUT_POST)) || $status == 'needs-action';
|
||||||
|
|
||||||
|
$error_msg = $this->gettext('errorimportingtask');
|
||||||
|
$success = false;
|
||||||
|
|
||||||
|
// successfully parsed tasks?
|
||||||
|
if ($task = $this->mail_get_itip_task($mbox, $uid, $mime_id)) {
|
||||||
|
// 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();
|
||||||
|
$list = $lists[$list_id] ?: $this->get_default_tasklist(true);
|
||||||
|
|
||||||
|
$metadata = array(
|
||||||
|
'uid' => $task['uid'],
|
||||||
|
'changed' => is_object($task['changed']) ? $task['changed']->format('U') : 0,
|
||||||
|
'sequence' => intval($task['sequence']),
|
||||||
|
'fallback' => strtoupper($status),
|
||||||
|
'method' => $this->ical->method,
|
||||||
|
'task' => 'tasks',
|
||||||
|
);
|
||||||
|
|
||||||
|
// update my attendee status according to submitted method
|
||||||
|
if (!empty($status)) {
|
||||||
|
$organizer = null;
|
||||||
|
$emails = $this->lib->get_user_emails();
|
||||||
|
|
||||||
|
foreach ($task['attendees'] as $i => $attendee) {
|
||||||
|
if ($attendee['role'] == 'ORGANIZER') {
|
||||||
|
$organizer = $attendee;
|
||||||
|
}
|
||||||
|
else if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
|
||||||
|
$metadata['attendee'] = $attendee['email'];
|
||||||
|
$metadata['rsvp'] = $attendee['role'] != 'NON-PARTICIPANT';
|
||||||
|
$reply_sender = $attendee['email'];
|
||||||
|
|
||||||
|
$task['attendees'][$i]['status'] = strtoupper($status);
|
||||||
|
if ($task['attendees'][$i]['status'] != 'NEEDS-ACTION') {
|
||||||
|
unset($task['attendees'][$i]['rsvp']); // remove RSVP attribute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add attendee with this user's default identity if not listed
|
||||||
|
if (!$reply_sender) {
|
||||||
|
$sender_identity = $this->rc->user->get_identity();
|
||||||
|
$task['attendees'][] = array(
|
||||||
|
'name' => $sender_identity['name'],
|
||||||
|
'email' => $sender_identity['email'],
|
||||||
|
'role' => 'OPT-PARTICIPANT',
|
||||||
|
'status' => strtoupper($status),
|
||||||
|
);
|
||||||
|
$metadata['attendee'] = $sender_identity['email'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save to tasklist
|
||||||
|
if ($list && $list['editable']) {
|
||||||
|
$task['list'] = $list['id'];
|
||||||
|
|
||||||
|
// check for existing task with the same UID
|
||||||
|
$existing = $this->driver->get_task($task['uid']);
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
// only update attendee status
|
||||||
|
if ($this->ical->method == 'REPLY') {
|
||||||
|
// try to identify the attendee using the email sender address
|
||||||
|
$existing_attendee = -1;
|
||||||
|
foreach ($existing['attendees'] as $i => $attendee) {
|
||||||
|
if ($task['_sender'] && ($attendee['email'] == $task['_sender'] || $attendee['email'] == $task['_sender_utf'])) {
|
||||||
|
$existing_attendee = $i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$task_attendee = null;
|
||||||
|
foreach ($task['attendees'] as $attendee) {
|
||||||
|
if ($task['_sender'] && ($attendee['email'] == $task['_sender'] || $attendee['email'] == $task['_sender_utf'])) {
|
||||||
|
$task_attendee = $attendee;
|
||||||
|
$metadata['fallback'] = $attendee['status'];
|
||||||
|
$metadata['attendee'] = $attendee['email'];
|
||||||
|
$metadata['rsvp'] = $attendee['rsvp'] || $attendee['role'] != 'NON-PARTICIPANT';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// found matching attendee entry in both existing and new events
|
||||||
|
if ($existing_attendee >= 0 && $task_attendee) {
|
||||||
|
$existing['attendees'][$existing_attendee] = $task_attendee;
|
||||||
|
$success = $this->driver->edit_task($existing);
|
||||||
|
}
|
||||||
|
// update the entire attendees block
|
||||||
|
else if (($task['sequence'] >= $existing['sequence'] || $task['changed'] >= $existing['changed']) && $task_attendee) {
|
||||||
|
$existing['attendees'][] = $task_attendee;
|
||||||
|
$success = $this->driver->edit_task($existing);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$error_msg = $this->gettext('newerversionexists');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// delete the task when declined
|
||||||
|
else if ($status == 'declined' && $delete) {
|
||||||
|
$deleted = $this->driver->delete_task($existing, true);
|
||||||
|
$success = true;
|
||||||
|
}
|
||||||
|
// import the (newer) task
|
||||||
|
else if ($task['sequence'] >= $existing['sequence'] || $task['changed'] >= $existing['changed']) {
|
||||||
|
$task['id'] = $existing['id'];
|
||||||
|
$task['list'] = $existing['list'];
|
||||||
|
|
||||||
|
// preserve my participant status for regular updates
|
||||||
|
if (empty($status)) {
|
||||||
|
$emails = $this->lib->get_user_emails();
|
||||||
|
foreach ($task['attendees'] as $i => $attendee) {
|
||||||
|
if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
|
||||||
|
foreach ($existing['attendees'] as $j => $_attendee) {
|
||||||
|
if ($attendee['email'] == $_attendee['email']) {
|
||||||
|
$task['attendees'][$i] = $existing['attendees'][$j];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set status=CANCELLED on CANCEL messages
|
||||||
|
if ($this->ical->method == 'CANCEL') {
|
||||||
|
$task['status'] = 'CANCELLED';
|
||||||
|
}
|
||||||
|
// show me as free when declined (#1670)
|
||||||
|
if ($status == 'declined' || $task['status'] == 'CANCELLED') {
|
||||||
|
$task['free_busy'] = 'free';
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = $this->driver->edit_task($task);
|
||||||
|
}
|
||||||
|
else if (!empty($status)) {
|
||||||
|
$existing['attendees'] = $task['attendees'];
|
||||||
|
if ($status == 'declined') { // show me as free when declined (#1670)
|
||||||
|
$existing['free_busy'] = 'free';
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = $this->driver->edit_event($existing);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$error_msg = $this->gettext('newerversionexists');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!$existing && ($status != 'declined' || $this->rc->config->get('kolab_invitation_tasklists'))) {
|
||||||
|
$success = $this->driver->create_task($task);
|
||||||
|
}
|
||||||
|
else if ($status == 'declined') {
|
||||||
|
$error_msg = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($status == 'declined') {
|
||||||
|
$error_msg = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$error_msg = $this->gettext('nowritetasklistfound');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
$message = $this->ical->method == 'REPLY' ? 'attendeupdateesuccess' : ($deleted ? 'successremoval' : ($existing ? 'updatedsuccessfully' : 'importedsuccessfully'));
|
||||||
|
$this->rc->output->command('display_message', $this->gettext(array('name' => $message, 'vars' => array('list' => $list['name']))), 'confirmation');
|
||||||
|
|
||||||
|
$metadata['rsvp'] = intval($metadata['rsvp']);
|
||||||
|
$metadata['after_action'] = $this->rc->config->get('tasklist_itip_after_action');
|
||||||
|
|
||||||
|
$this->rc->output->command('plugin.itip_message_processed', $metadata);
|
||||||
|
$error_msg = null;
|
||||||
|
}
|
||||||
|
else if ($error_msg) {
|
||||||
|
$this->rc->output->command('display_message', $error_msg, 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
// send iTip reply
|
||||||
|
if ($this->ical->method == 'REQUEST' && $organizer && !$noreply && !in_array(strtolower($organizer['email']), $emails) && !$error_msg) {
|
||||||
|
$task['comment'] = rcube_utils::get_input_value('_comment', rcube_utils::INPUT_POST);
|
||||||
|
$itip = $this->load_itip();
|
||||||
|
$itip->set_sender_email($reply_sender);
|
||||||
|
|
||||||
|
if ($itip->send_itip_message($task, 'REPLY', $organizer, 'itipsubject' . $status, 'itipmailbody' . $status))
|
||||||
|
$this->rc->output->command('display_message', $this->gettext(array('name' => 'sentresponseto', 'vars' => array('mailto' => $organizer['name'] ? $organizer['name'] : $organizer['email']))), 'confirmation');
|
||||||
|
else
|
||||||
|
$this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->rc->output->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**** Task invitation plugin hooks ****/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for calendar/itip-status requests
|
||||||
|
*/
|
||||||
|
function task_itip_status()
|
||||||
|
{
|
||||||
|
$data = rcube_utils::get_input_value('data', rcube_utils::INPUT_POST, true);
|
||||||
|
|
||||||
|
// find local copy of the referenced task
|
||||||
|
$existing = $this->driver->get_task($data);
|
||||||
|
$itip = $this->load_itip();
|
||||||
|
$response = $itip->get_itip_status($data, $existing);
|
||||||
|
|
||||||
|
// get a list of writeable lists to save new tasks to
|
||||||
|
if (!$existing && $response['action'] == 'rsvp' || $response['action'] == 'import') {
|
||||||
|
$lists = $this->driver->get_lists();
|
||||||
|
$select = new html_select(array('name' => 'tasklist', 'id' => 'itip-saveto', 'is_escaped' => true));
|
||||||
|
$num = 0;
|
||||||
|
|
||||||
|
foreach ($lists as $list) {
|
||||||
|
if ($list['editable']) {
|
||||||
|
$select->add($list['name'], $list['id']);
|
||||||
|
$num++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($num <= 1) {
|
||||||
|
$select = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($select) {
|
||||||
|
$default_list = $this->get_default_tasklist(true);
|
||||||
|
$response['select'] = html::span('folder-select', $this->gettext('saveintasklist') . ' ' .
|
||||||
|
$select->show($this->rc->config->get('tasklist_default_list', $default_list['id'])));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->rc->output->command('plugin.update_itip_object_status', $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for calendar/itip-remove requests
|
||||||
|
*/
|
||||||
|
function task_itip_remove()
|
||||||
|
{
|
||||||
|
$success = false;
|
||||||
|
$uid = rcube_utils::get_input_value('uid', rcube_utils::INPUT_POST);
|
||||||
|
|
||||||
|
// search for event if only UID is given
|
||||||
|
if ($task = $this->driver->get_task($uid)) {
|
||||||
|
$success = $this->driver->delete_task($task, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
$this->rc->output->show_message('tasklist.successremoval', 'confirmation');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->rc->output->show_message('tasklist.errorsaving', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/******* Utility functions *******/
|
/******* Utility functions *******/
|
||||||
|
|
||||||
|
@ -1275,4 +1806,3 @@ class tasklist extends rcube_plugin
|
||||||
return $this->driver->user_delete($args);
|
return $this->driver->user_delete($args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ function rcube_tasklist(settings)
|
||||||
/* public methods */
|
/* public methods */
|
||||||
this.create_from_mail = create_from_mail;
|
this.create_from_mail = create_from_mail;
|
||||||
this.mail2taskdialog = mail2task_dialog;
|
this.mail2taskdialog = mail2task_dialog;
|
||||||
|
this.save_to_tasklist = save_to_tasklist;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,14 +81,29 @@ function rcube_tasklist(settings)
|
||||||
this.ui.edit_task(null, 'new', prop);
|
this.ui.edit_task(null, 'new', prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handler for attachment-save-tasklist commands
|
||||||
|
function save_to_tasklist()
|
||||||
|
{
|
||||||
|
// TODO: show dialog to select the tasklist for importing
|
||||||
|
if (this.selected_attachment && window.rcube_libcalendaring) {
|
||||||
|
rcmail.http_post('tasks/mailimportattach', {
|
||||||
|
_uid: rcmail.env.uid,
|
||||||
|
_mbox: rcmail.env.mailbox,
|
||||||
|
_part: this.selected_attachment,
|
||||||
|
// _list: $('#tasklist-attachment-saveto').val(),
|
||||||
|
}, rcmail.set_busy(true, 'itip.savingdata'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tasklist plugin initialization (for email task) */
|
/* tasklist plugin initialization (for email task) */
|
||||||
window.rcmail && rcmail.env.task == 'mail' && rcmail.addEventListener('init', function(evt) {
|
window.rcmail && rcmail.env.task == 'mail' && rcmail.addEventListener('init', function(evt) {
|
||||||
var tasks = new rcube_tasklist(rcmail.env.libcal_settings);
|
var tasks = new rcube_tasklist(rcmail.env.libcal_settings);
|
||||||
|
|
||||||
rcmail.register_command('tasklist-create-from-mail', function() { tasks.create_from_mail() });
|
rcmail.register_command('tasklist-create-from-mail', function() { tasks.create_from_mail(); });
|
||||||
rcmail.addEventListener('plugin.mail2taskdialog', function(p){ tasks.mail2taskdialog(p) });
|
rcmail.register_command('attachment-save-task', function() { tasks.save_to_tasklist(); });
|
||||||
|
rcmail.addEventListener('plugin.mail2taskdialog', function(p) { tasks.mail2taskdialog(p); });
|
||||||
rcmail.addEventListener('plugin.unlock_saving', function(p) { tasks.ui && tasks.ui.unlock_saving(); });
|
rcmail.addEventListener('plugin.unlock_saving', function(p) { tasks.ui && tasks.ui.unlock_saving(); });
|
||||||
|
|
||||||
if (rcmail.env.action != 'show')
|
if (rcmail.env.action != 'show')
|
||||||
|
@ -95,6 +111,16 @@ window.rcmail && rcmail.env.task == 'mail' && rcmail.addEventListener('init', fu
|
||||||
else
|
else
|
||||||
rcmail.enable_command('tasklist-create-from-mail', true);
|
rcmail.enable_command('tasklist-create-from-mail', true);
|
||||||
|
|
||||||
|
rcmail.addEventListener('beforemenu-open', function(p) {
|
||||||
|
if (p.menu == 'attachmentmenu') {
|
||||||
|
tasks.selected_attachment = p.id;
|
||||||
|
var mimetype = rcmail.env.attachments[p.id],
|
||||||
|
is_ics = mimetype == 'text/calendar' || mimetype == 'text/x-vcalendar' || mimetype == 'application/ics';
|
||||||
|
|
||||||
|
rcmail.enable_command('attachment-save-task', is_ics);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// add contextmenu item
|
// add contextmenu item
|
||||||
if (window.rcm_contextmenu_register_command) {
|
if (window.rcm_contextmenu_register_command) {
|
||||||
rcm_contextmenu_register_command(
|
rcm_contextmenu_register_command(
|
||||||
|
@ -104,4 +130,3 @@ window.rcmail && rcmail.env.task == 'mail' && rcmail.addEventListener('init', fu
|
||||||
'moveto');
|
'moveto');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue