From f01a600af44057dc2310c8df3982da35ec1d8252 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Thu, 6 Nov 2014 12:28:58 +0100 Subject: [PATCH] Enable iTip delegation for tasks (#3860) --- plugins/libcalendaring/libcalendaring.js | 2 +- plugins/tasklist/localization/en_US.inc | 2 + plugins/tasklist/tasklist.js | 41 ++++++++++-- plugins/tasklist/tasklist.php | 84 +++++++++++++++++++----- 4 files changed, 105 insertions(+), 24 deletions(-) diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js index f06b55a0..d04a024c 100644 --- a/plugins/libcalendaring/libcalendaring.js +++ b/plugins/libcalendaring/libcalendaring.js @@ -863,7 +863,7 @@ rcube_libcalendaring.itip_delegate_dialog = function(callback, selector) rcmail.gettext('itip.itipcomment') + '">' + '' + '
' + - (selector ? selector.html() : '') + + (selector && selector.length ? selector.html() : '') + '
' + ''; diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc index 8593fe5b..68741210 100644 --- a/plugins/tasklist/localization/en_US.inc +++ b/plugins/tasklist/localization/en_US.inc @@ -153,6 +153,8 @@ $labels['itipmailbodydeclined'] = "\$sender has declined the assignment to the f $labels['itipmailbodycancel'] = "\$sender has rejected your assignment to the following task:\n\n*\$title*\n\nDue: \$date"; $labels['itipmailbodyin-process'] = "\$sender has set the status of the following task to in-process:\n\n*\$title*\n\nDue: \$date"; $labels['itipmailbodycompleted'] = "\$sender has completed the following task:\n\n*\$title*\n\nDue: \$date"; +$labels['itipmailbodydelegated'] = "\$sender has delegated the following task:\n\n*\$title*\n\nDue: \$date"; +$labels['itipmailbodydelegatedto'] = "\$sender has delegated the following task to you:\n\n*\$title*\n\nDue: \$date"; $labels['attendeeaccepted'] = 'Assignee has accepted'; $labels['attendeetentative'] = 'Assignee has tentatively accepted'; diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js index 7683cc5f..f107a39c 100644 --- a/plugins/tasklist/tasklist.js +++ b/plugins/tasklist/tasklist.js @@ -533,17 +533,41 @@ function rcube_tasklist_ui(settings) } }); - // init RSVP widget - $('#task-rsvp input.button').click(function(e) { - var response = $(this).attr('rel'); - + /** + * + */ + function task_rsvp(response, delegate) + { if (me.selected_task && me.selected_task.attendees && response) { + // bring up delegation dialog + if (response == 'delegated' && !delegate) { + rcube_libcalendaring.itip_delegate_dialog(function(data) { + $('#reply-comment-task-rsvp').val(data.comment); + data.rsvp = data.rsvp ? 1 : ''; + task_rsvp('delegated', data); + }); + return; + } + // update attendee status for (var data, i=0; i < me.selected_task.attendees.length; i++) { data = me.selected_task.attendees[i]; if (settings.identity.emails.indexOf(';'+String(data.email).toLowerCase()) >= 0) { data.status = response.toUpperCase(); - delete data.rsvp; // unset RSVP flag + + if (data.status == 'DELEGATED') { + data['delegated-to'] = delegate.to; + } + else { + delete data.rsvp; // unset RSVP flag + + if (data['delegated-to']) { + delete data['delegated-to']; + if (data.role == 'NON-PARTICIPANT' && data.status != 'DECLINED') { + data.role = 'REQ-PARTICIPANT'; + } + } + } } } @@ -551,7 +575,7 @@ function rcube_tasklist_ui(settings) saving_lock = rcmail.set_busy(true, 'tasklist.savingdata'); rcmail.http_post('tasks/task', { action: 'rsvp', - t: me.selected_task, + t: $.extend({}, me.selected_task, (delegate || {})), filter: filtermask, status: response, noreply: $('#noreply-task-rsvp:checked').length ? 1 : 0, @@ -560,6 +584,11 @@ function rcube_tasklist_ui(settings) task_show_dialog(me.selected_task.id); } + } + + // init RSVP widget + $('#task-rsvp input.button').click(function(e) { + task_rsvp($(this).attr('rel')) }); // register click handler for message links diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php index 5f34e86c..0ddba7dc 100644 --- a/plugins/tasklist/tasklist.php +++ b/plugins/tasklist/tasklist.php @@ -120,6 +120,7 @@ class tasklist extends rcube_plugin $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->register_action('itip-delegate', array($this, 'mail_itip_delegate')); $this->add_hook('refresh', array($this, 'refresh')); $this->collapsed_tasks = array_filter(explode(',', $this->rc->config->get('tasklist_collapsed_tasks', ''))); @@ -245,6 +246,7 @@ class tasklist extends rcube_plugin } case 'edit': + $oldrec = $this->driver->get_task($rec); $rec = $this->prepare_task($rec); $clone = $this->handle_recurrence($rec, $this->driver->get_task($rec)); if ($success = $this->driver->edit_task($rec)) { @@ -357,13 +359,24 @@ class tasklist extends rcube_plugin case 'rsvp': $status = rcube_utils::get_input_value('status', rcube_utils::INPUT_GPC); + $noreply = intval(rcube_utils::get_input_value('noreply', rcube_utils::INPUT_GPC)) || $status == 'needs-action'; $task = $this->driver->get_task($rec); $task['attendees'] = $rec['attendees']; + $task['_type'] = 'task'; + + // send invitation to delegatee + add it as attendee + if ($status == 'delegated' && $rec['to']) { + $itip = $this->load_itip(); + if ($itip->delegate_to($task, $rec['to'], (bool)$rec['rsvp'])) { + $this->rc->output->show_message('tasklist.itipsendsuccess', 'confirmation'); + $refresh[] = $task; + $noreply = false; + } + } + $rec = $task; if ($success = $this->driver->edit_task($rec)) { - $noreply = intval(rcube_utils::get_input_value('noreply', rcube_utils::INPUT_GPC)) || $status == 'needs-action'; - if (!$noreply) { // let the reply clause further down send the iTip message $rec['_reportpartstat'] = $status; @@ -439,7 +452,7 @@ class tasklist extends rcube_plugin if (!$this->itip) { require_once realpath(__DIR__ . '/../libcalendaring/lib/libcalendaring_itip.php'); $this->itip = new libcalendaring_itip($this, 'tasklist'); - $this->itip->set_rsvp_actions(array('accepted','declined')); + $this->itip->set_rsvp_actions(array('accepted','declined','delegated')); $this->itip->set_rsvp_status(array('accepted','tentative','declined','delegated','in-process','completed')); } @@ -1701,15 +1714,45 @@ class tasklist extends rcube_plugin $error_msg = $this->gettext('errorimportingtask'); $success = false; + $delegate = null; + + if ($status == 'delegated') { + $delegates = rcube_mime::decode_address_list(rcube_utils::get_input_value('_to', rcube_utils::INPUT_POST, true), 1, false); + $delegate = reset($delegates); + + if (empty($delegate) || empty($delegate['mailto'])) { + $this->rc->output->command('display_message', $this->gettext('libcalendaring.delegateinvalidaddress'), 'error'); + return; + } + } // successfully parsed tasks? if ($task = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'task')) { $task = $this->from_ical($task); + // forward iTip request to delegatee + if ($delegate) { + $rsvpme = intval(rcube_utils::get_input_value('_rsvp', rcube_utils::INPUT_POST)); + + $itip = $this->load_itip(); + if ($itip->delegate_to($task, $delegate, $rsvpme ? true : false)) { + $this->rc->output->show_message('tasklist.itipsendsuccess', 'confirmation'); + } + else { + $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error'); + } + } + // find writeable list to store the task $list_id = !empty($_REQUEST['_folder']) ? rcube_utils::get_input_value('_folder', rcube_utils::INPUT_POST) : null; $lists = $this->driver->get_lists(); - $list = $lists[$list_id] ?: $this->get_default_tasklist(true, $task['sensitivity'] == 'confidential'); + $list = $lists[$list_id]; + $dontsave = ($_REQUEST['_folder'] === '' && $task['_method'] == 'REQUEST'); + + // select default list except user explicitly selected 'none' + if (!$list && !$dontsave) { + $list = $this->get_default_tasklist(true, $task['sensitivity'] == 'confidential'); + } $metadata = array( 'uid' => $task['uid'], @@ -1732,7 +1775,7 @@ class tasklist extends rcube_plugin $reply_sender = $attendee['email']; $task['attendees'][$i]['status'] = strtoupper($status); - if ($task['attendees'][$i]['status'] != 'NEEDS-ACTION') { + if (!in_array($task['attendees'][$i]['status'], array('NEEDS-ACTION','DELEGATED'))) { unset($task['attendees'][$i]['rsvp']); // remove RSVP attribute } } @@ -1850,7 +1893,7 @@ class tasklist extends rcube_plugin $error_msg = null; } } - else if ($status == 'declined') { + else if ($status == 'declined' || $dontsave) { $error_msg = null; } else { @@ -1858,9 +1901,11 @@ class tasklist extends rcube_plugin } } - if ($success) { - $message = $task['_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'); + if ($success || $dontsave) { + if ($success) { + $message = $task['_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('calendar_itip_after_action', 0); @@ -1891,7 +1936,17 @@ class tasklist extends rcube_plugin /**** Task invitation plugin hooks ****/ /** - * Handler for calendar/itip-status requests + * Handler for task/itip-delegate requests + */ + function mail_itip_delegate() + { + // forward request to mail_import_itip() with the right status + $_POST['_status'] = $_REQUEST['_status'] = 'delegated'; + $this->mail_import_itip(); + } + + /** + * Handler for task/itip-status requests */ public function task_itip_status() { @@ -1906,18 +1961,13 @@ class tasklist extends rcube_plugin 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; + $select->add('--', ''); foreach ($lists as $list) { if ($list['editable']) { $select->add($list['name'], $list['id']); - $num++; } } - - if ($num <= 1) { - $select = null; - } } if ($select) { @@ -1930,7 +1980,7 @@ class tasklist extends rcube_plugin } /** - * Handler for calendar/itip-remove requests + * Handler for task/itip-remove requests */ public function task_itip_remove() {