6 commits - plugins/calendar plugins/libcalendaring plugins/libkolab plugins/tasklist
Thomas Brüderli
bruederli at kolabsys.com
Thu Nov 6 17:10:20 CET 2014
plugins/calendar/calendar.php | 97 +++++++++++++++--
plugins/calendar/calendar_ui.js | 33 +++++
plugins/calendar/lib/calendar_ui.php | 6 -
plugins/calendar/localization/ca_ES.inc | 4
plugins/calendar/localization/da_DK.inc | 4
plugins/calendar/localization/de_DE.inc | 4
plugins/calendar/localization/en_US.inc | 6 -
plugins/calendar/localization/es_AR.inc | 4
plugins/calendar/localization/fi_FI.inc | 4
plugins/calendar/localization/fr_FR.inc | 4
plugins/calendar/localization/hu_HU.inc | 4
plugins/calendar/localization/nl_NL.inc | 4
plugins/calendar/localization/pl_PL.inc | 4
plugins/calendar/localization/pt_BR.inc | 4
plugins/calendar/localization/ru_RU.inc | 4
plugins/libcalendaring/lib/libcalendaring_itip.php | 117 ++++++++++++++++++---
plugins/libcalendaring/libcalendaring.js | 103 ++++++++++++++++++
plugins/libcalendaring/libcalendaring.php | 14 ++
plugins/libcalendaring/libvcalendar.php | 3
plugins/libcalendaring/localization/en_US.inc | 7 +
plugins/libcalendaring/skins/larry/libcal.css | 8 +
plugins/libkolab/lib/kolab_format_xcal.php | 9 +
plugins/tasklist/localization/en_US.inc | 3
plugins/tasklist/tasklist.js | 41 ++++++-
plugins/tasklist/tasklist.php | 117 +++++++++++++++++----
25 files changed, 515 insertions(+), 93 deletions(-)
New commits:
commit c6f5a8233bf1a52841a7bfd2123e1d3f838413d8
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Thu Nov 6 17:09:49 2014 +0100
Fix handling of RSVP flags
diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 910affb..e359712 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -1642,7 +1642,9 @@ class calendar extends rcube_plugin
foreach ((array)$event['attendees'] as $i => $attendee) {
if ($attendee['role'] == 'ORGANIZER') {
$organizer = $attendee;
- break;
+ }
+ if ($attendee['status'] == 'DELEGATED' && $attendee['rsvp'] == false) {
+ $event['attendees'][$i]['noreply'] = true;
}
}
@@ -1895,7 +1897,7 @@ class calendar extends rcube_plugin
// compose multipart message using PEAR:Mail_Mime
$method = $action == 'remove' ? 'CANCEL' : 'REQUEST';
- $message = $itip->compose_itip_message($event, $method);
+ $message = $itip->compose_itip_message($event, $method, $event['sequence'] > $old['sequence']);
// list existing attendees from $old event
$old_attendees = array();
@@ -1915,7 +1917,11 @@ class calendar extends rcube_plugin
// skip if notification is disabled for this attendee
if ($attendee['noreply'] && $itip_notify & 2)
continue;
-
+
+ // skip if this attendee has delegated and set RSVP=FALSE
+ if ($attendee['status'] == 'DELEGATED' && $attendee['rsvp'] === false)
+ continue;
+
// which template to use for mail text
$is_new = !in_array($attendee['email'], $old_attendees);
$is_rsvp = $is_new || $event['sequence'] > $old['sequence'];
@@ -2639,7 +2645,8 @@ class calendar extends rcube_plugin
else if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
$event['attendees'][$i]['status'] = strtoupper($status);
if (!in_array($event['attendees'][$i]['status'], array('NEEDS-ACTION','DELEGATED')))
- unset($event['attendees'][$i]['rsvp']); // remove RSVP attribute
+ $event['attendees'][$i]['rsvp'] = false; // unset RSVP attribute
+
$metadata['attendee'] = $attendee['email'];
$metadata['rsvp'] = $attendee['role'] != 'NON-PARTICIPANT';
$reply_sender = $attendee['email'];
@@ -2696,7 +2703,17 @@ class calendar extends rcube_plugin
$existing['attendees'][] = $attendee;
}
}
-
+
+ // if delegatee has declined, set delegator's RSVP=True
+ if ($event_attendee && $event_attendee['status'] == 'DECLINED' && $event_attendee['delegated-from']) {
+ foreach ($existing['attendees'] as $i => $attendee) {
+ if ($attendee['email'] == $event_attendee['delegated-from']) {
+ $existing['attendees'][$i]['rsvp'] = true;
+ break;
+ }
+ }
+ }
+
// found matching attendee entry in both existing and new events
if ($existing_attendee >= 0 && $event_attendee) {
$existing['attendees'][$existing_attendee] = $event_attendee;
diff --git a/plugins/libcalendaring/lib/libcalendaring_itip.php b/plugins/libcalendaring/lib/libcalendaring_itip.php
index 9524249..bd83f16 100644
--- a/plugins/libcalendaring/lib/libcalendaring_itip.php
+++ b/plugins/libcalendaring/lib/libcalendaring_itip.php
@@ -90,6 +90,7 @@ class libcalendaring_itip
* @param string Mail subject
* @param string Mail body text label
* @param object Mail_mime object with message data
+ * @param boolean Request RSVP
* @return boolean True on success, false on failure
*/
public function send_itip_message($event, $method, $recipient, $subject, $bodytext, $message = null, $rsvp = true)
@@ -98,7 +99,7 @@ class libcalendaring_itip
$this->sender['name'] = $this->sender['email'];
if (!$message)
- $message = $this->compose_itip_message($event, $method);
+ $message = $this->compose_itip_message($event, $method, $rsvp);
$mailto = rcube_idn_to_ascii($recipient['email']);
@@ -192,9 +193,10 @@ class libcalendaring_itip
*
* @param array Event object to send
* @param string iTip method (REQUEST|REPLY|CANCEL)
+ * @param boolean Request RSVP
* @return object Mail_mime object with message data
*/
- public function compose_itip_message($event, $method)
+ public function compose_itip_message($event, $method, $rsvp = true)
{
$from = rcube_idn_to_ascii($this->sender['email']);
$from_utf = rcube_utils::idn_to_utf8($from);
@@ -228,11 +230,11 @@ class libcalendaring_itip
$event['attendees'] = $reply_attendees;
}
}
- // set RSVP=TRUE for every attendee if not set
+ // set RSVP for every attendee
else if ($method == 'REQUEST') {
foreach ($event['attendees'] as $i => $attendee) {
- if (!isset($attendee['rsvp'])) {
- $event['attendees'][$i]['rsvp']= true;
+ if ($attendee['status'] != 'DELEGATED') {
+ $event['attendees'][$i]['rsvp']= $rsvp ? true : null;
}
}
}
diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php
index 1e10ddc..062ee7e 100644
--- a/plugins/libcalendaring/libvcalendar.php
+++ b/plugins/libcalendaring/libvcalendar.php
@@ -1072,7 +1072,8 @@ class libvcalendar implements Iterator
$event['organizer'] = $attendee;
}
else if (!empty($attendee['email'])) {
- $attendee['rsvp'] = $attendee['rsvp'] ? 'TRUE' : null;
+ if (isset($attendee['rsvp']))
+ $attendee['rsvp'] = $attendee['rsvp'] ? 'TRUE' : 'FALSE';
$ve->add('ATTENDEE', 'mailto:' . $attendee['email'], array_filter(self::map_keys($attendee, $this->attendee_keymap)));
}
}
diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php
index 08f27d0..ad54505 100644
--- a/plugins/libkolab/lib/kolab_format_xcal.php
+++ b/plugins/libkolab/lib/kolab_format_xcal.php
@@ -364,14 +364,17 @@ abstract class kolab_format_xcal extends kolab_format
$cr = new ContactReference(ContactReference::EmailReference, $attendee['email']);
$cr->setName($attendee['name']);
+ // set attendee RSVP if missing
+ if (!isset($attendee['rsvp'])) {
+ $object['attendees'][$i]['rsvp'] = $attendee['rsvp'] = true;
+ }
+
$att = new Attendee;
$att->setContact($cr);
$att->setPartStat($this->part_status_map[$attendee['status']]);
$att->setRole($this->role_map[$attendee['role']] ? $this->role_map[$attendee['role']] : kolabformat::Required);
$att->setCutype($this->cutype_map[$attendee['cutype']] ? $this->cutype_map[$attendee['cutype']] : kolabformat::CutypeIndividual);
- $att->setRSVP((bool)$attendee['rsvp'] || $reschedule);
-
- $object['attendees'][$i]['rsvp'] = $attendee['rsvp'] || $reschedule;
+ $att->setRSVP((bool)$attendee['rsvp']);
if (!empty($attendee['delegated-from'])) {
$vdelegators = new vectorcontactref;
diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php
index 71e6431..3f23c59 100644
--- a/plugins/tasklist/tasklist.php
+++ b/plugins/tasklist/tasklist.php
@@ -757,7 +757,7 @@ class tasklist extends rcube_plugin
// compose multipart message using PEAR:Mail_Mime
$method = $action == 'delete' ? 'CANCEL' : 'REQUEST';
$object = $this->to_libcal($task);
- $message = $itip->compose_itip_message($object, $method);
+ $message = $itip->compose_itip_message($object, $method, $task['sequence'] > $old['sequence']);
// list existing attendees from the $old task
$old_attendees = array();
@@ -779,6 +779,12 @@ class tasklist extends rcube_plugin
if ($attendee['noreply'] && $itip_notify & 2) {
continue;
}
+
+ // skip if this attendee has delegated and set RSVP=FALSE
+ if ($attendee['status'] == 'DELEGATED' && $attendee['rsvp'] === false) {
+ continue;
+ }
+
// which template to use for mail text
$is_new = !in_array($attendee['email'], $old_attendees);
$is_rsvp = $is_new || $task['sequence'] > $old['sequence'];
@@ -1776,7 +1782,7 @@ class tasklist extends rcube_plugin
$task['attendees'][$i]['status'] = strtoupper($status);
if (!in_array($task['attendees'][$i]['status'], array('NEEDS-ACTION','DELEGATED'))) {
- unset($task['attendees'][$i]['rsvp']); // remove RSVP attribute
+ $task['attendees'][$i]['rsvp'] = false; // unset RSVP attribute
}
}
}
@@ -1833,6 +1839,16 @@ class tasklist extends rcube_plugin
}
}
+ // if delegatee has declined, set delegator's RSVP=True
+ if ($task_attendee && $task_attendee['status'] == 'DECLINED' && $task_attendee['delegated-from']) {
+ foreach ($existing['attendees'] as $i => $attendee) {
+ if ($attendee['email'] == $task_attendee['delegated-from']) {
+ $existing['attendees'][$i]['rsvp'] = true;
+ break;
+ }
+ }
+ }
+
// found matching attendee entry in both existing and new events
if ($existing_attendee >= 0 && $task_attendee) {
$existing['attendees'][$existing_attendee] = $task_attendee;
commit 7294ef8be0594ec32476ba49a34282675919f38b
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Thu Nov 6 15:14:44 2014 +0100
Send delegate attendee in iTip reply (as suggested in RFC 5546) + add it to organizers calendar/tasklist
diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 88a4351..910affb 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -2671,10 +2671,11 @@ class calendar extends rcube_plugin
if ($event['_method'] == 'REPLY') {
// try to identify the attendee using the email sender address
$existing_attendee = -1;
+ $existing_attendee_emails = array();
foreach ($existing['attendees'] as $i => $attendee) {
+ $existing_attendee_emails[] = $attendee['email'];
if ($event['_sender'] && ($attendee['email'] == $event['_sender'] || $attendee['email'] == $event['_sender_utf'])) {
$existing_attendee = $i;
- break;
}
}
$event_attendee = null;
@@ -2684,7 +2685,15 @@ class calendar extends rcube_plugin
$metadata['fallback'] = $attendee['status'];
$metadata['attendee'] = $attendee['email'];
$metadata['rsvp'] = $attendee['rsvp'] || $attendee['role'] != 'NON-PARTICIPANT';
- break;
+ if ($attendee['status'] != 'DELEGATED') {
+ break;
+ }
+ }
+ // also copy delegate attendee
+ else if (!empty($attendee['delegated-from']) &&
+ (stripos($attendee['delegated-from'], $event['_sender']) !== false || stripos($attendee['delegated-from'], $event['_sender_utf']) !== false) &&
+ (!in_array($attendee['email'], $existing_attendee_emails))) {
+ $existing['attendees'][] = $attendee;
}
}
diff --git a/plugins/libcalendaring/lib/libcalendaring_itip.php b/plugins/libcalendaring/lib/libcalendaring_itip.php
index 9403f46..9524249 100644
--- a/plugins/libcalendaring/lib/libcalendaring_itip.php
+++ b/plugins/libcalendaring/lib/libcalendaring_itip.php
@@ -197,24 +197,34 @@ class libcalendaring_itip
public function compose_itip_message($event, $method)
{
$from = rcube_idn_to_ascii($this->sender['email']);
- $from_utf = rcube_idn_to_utf8($from);
+ $from_utf = rcube_utils::idn_to_utf8($from);
$sender = format_email_recipient($from, $this->sender['name']);
// truncate list attendees down to the recipient of the iTip Reply.
// constraints for a METHOD:REPLY according to RFC 5546
if ($method == 'REPLY') {
- $replying_attendee = null; $reply_attendees = array();
+ $replying_attendee = null;
+ $reply_attendees = array();
foreach ($event['attendees'] as $attendee) {
if ($attendee['role'] == 'ORGANIZER') {
$reply_attendees[] = $attendee;
}
- else if (strcasecmp($attedee['email'], $from) == 0 || strcasecmp($attendee['email'], $from_utf) == 0) {
+ else if (strcasecmp($attendee['email'], $from) == 0 || strcasecmp($attendee['email'], $from_utf) == 0) {
$replying_attendee = $attendee;
- unset($replying_attendee['rsvp']); // unset the RSVP attribute
+ if ($attendee['status'] != 'DELEGATED') {
+ unset($replying_attendee['rsvp']); // unset the RSVP attribute
+ }
+ }
+ // include attendees relevant for delegation (RFC 5546, Section 4.2.5)
+ else if ((!empty($attendee['delegated-to']) &&
+ (strcasecmp($attendee['delegated-to'], $from) == 0 || strcasecmp($attendee['delegated-to'], $from_utf) == 0)) ||
+ (!empty($attendee['delegated-from']) &&
+ (strcasecmp($attendee['delegated-from'], $from) == 0 || strcasecmp($attendee['delegated-from'], $from_utf) == 0))) {
+ $reply_attendees[] = $attendee;
}
}
if ($replying_attendee) {
- $reply_attendees[] = $replying_attendee;
+ array_unshift($reply_attendees, $replying_attendee);
$event['attendees'] = $reply_attendees;
}
}
@@ -454,7 +464,8 @@ class libcalendaring_itip
$title = $this->gettext('itipreply');
foreach ($event['attendees'] as $attendee) {
- if (!empty($attendee['email']) && $attendee['role'] != 'ORGANIZER') {
+ if (!empty($attendee['email']) && $attendee['role'] != 'ORGANIZER' &&
+ (empty($event['_sender']) || ($attendee['email'] == $event['_sender'] || $attendee['email'] == $event['_sender_utf']))) {
$metadata['attendee'] = $attendee['email'];
$rsvp_status = strtoupper($attendee['status']);
if ($attendee['delegated-to'])
diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
index 6e61add..78eaa24 100644
--- a/plugins/libcalendaring/libcalendaring.php
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -59,6 +59,7 @@ class libcalendaring extends rcube_plugin
);
private static $instance;
+ private static $email_regex = '/([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))/';
private $mail_ical_parser;
@@ -1287,6 +1288,15 @@ class libcalendaring extends rcube_plugin
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;
+
+ // store the message's sender address for comparisons
+ $this->mail_ical_parser->sender = preg_match(self::$email_regex, $this->ical_message->headers->from, $m) ? $m[1] : '';
+ if (!empty($this->mail_ical_parser->sender)) {
+ foreach ($this->mail_ical_parser->objects as $i => $object) {
+ $this->mail_ical_parser->objects[$i]['_sender'] = $this->mail_ical_parser->sender;
+ $this->mail_ical_parser->objects[$i]['_sender_utf'] = rcube_utils::idn_to_utf8($this->mail_ical_parser->sender);
+ }
+ }
break;
}
}
@@ -1335,8 +1345,8 @@ class libcalendaring extends rcube_plugin
$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']);
+ $object['_sender'] = preg_match(self::$email_regex, $headers->from, $m) ? $m[1] : '';
+ $object['_sender_utf'] = rcube_utils::idn_to_utf8($object['_sender']);
return $object;
}
diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc
index 6874121..60ca18c 100644
--- a/plugins/tasklist/localization/en_US.inc
+++ b/plugins/tasklist/localization/en_US.inc
@@ -171,6 +171,7 @@ $labels['itipcommenttitle'] = 'This comment will be attached to the invitation/n
$labels['itipsendsuccess'] = 'Notification sent to assignees';
$labels['errornotifying'] = 'Failed to send notifications to task assignees';
$labels['removefromcalendar'] = 'Remove from my tasks';
+$labels['delegateinvitation'] = 'Delegate assignment';
$labels['andnmore'] = '$nr more...';
$labels['delegatedto'] = 'Delegated to: ';
diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php
index 0ddba7d..71e6431 100644
--- a/plugins/tasklist/tasklist.php
+++ b/plugins/tasklist/tasklist.php
@@ -1806,10 +1806,11 @@ class tasklist extends rcube_plugin
if ($task['_method'] == 'REPLY') {
// try to identify the attendee using the email sender address
$existing_attendee = -1;
+ $existing_attendee_emails = array();
foreach ($existing['attendees'] as $i => $attendee) {
+ $existing_attendee_emails[] = $attendee['email'];
if ($task['_sender'] && ($attendee['email'] == $task['_sender'] || $attendee['email'] == $task['_sender_utf'])) {
$existing_attendee = $i;
- break;
}
}
@@ -1820,7 +1821,15 @@ class tasklist extends rcube_plugin
$metadata['fallback'] = $attendee['status'];
$metadata['attendee'] = $attendee['email'];
$metadata['rsvp'] = $attendee['rsvp'] || $attendee['role'] != 'NON-PARTICIPANT';
- break;
+ if ($attendee['status'] != 'DELEGATED') {
+ break;
+ }
+ }
+ // also copy delegate attendee
+ else if (!empty($attendee['delegated-from']) &&
+ (stripos($attendee['delegated-from'], $task['_sender']) !== false || stripos($attendee['delegated-from'], $task['_sender_utf']) !== false) &&
+ (!in_array($attendee['email'], $existing_attendee_emails))) {
+ $existing['attendees'][] = $attendee;
}
}
commit f01a600af44057dc2310c8df3982da35ec1d8252
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Thu Nov 6 12:28:58 2014 +0100
Enable iTip delegation for tasks (#3860)
diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js
index f06b55a..d04a024 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') + '"></textarea>' +
'</div>' +
'<div class="form-section">' +
- (selector ? selector.html() : '') +
+ (selector && selector.length ? selector.html() : '') +
'</div>' +
'</form>';
diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc
index 8593fe5..6874121 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 7683cc5..f107a39 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 5f34e86..0ddba7d 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()
{
commit c26f9d25cba791ec5e4735e4c7a14ee1c1b30469
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Thu Nov 6 12:06:51 2014 +0100
Some fixes to the iTip delegation functions
diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 2b37b79..88a4351 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -2638,7 +2638,7 @@ class calendar extends rcube_plugin
}
else if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
$event['attendees'][$i]['status'] = strtoupper($status);
- if ($event['attendees'][$i]['status'] != 'NEEDS-ACTION')
+ if (!in_array($event['attendees'][$i]['status'], array('NEEDS-ACTION','DELEGATED')))
unset($event['attendees'][$i]['rsvp']); // remove RSVP attribute
$metadata['attendee'] = $attendee['email'];
$metadata['rsvp'] = $attendee['role'] != 'NON-PARTICIPANT';
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index ae745fe..32d4773 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -2349,7 +2349,7 @@ function rcube_calendar_ui(settings)
if (data['delegated-to']) {
delete data['delegated-to'];
- if (data.role == 'NON-PARTICIPANT' && status != 'DECLINED')
+ if (data.role == 'NON-PARTICIPANT' && data.status != 'DECLINED')
data.role = 'REQ-PARTICIPANT';
}
}
diff --git a/plugins/libcalendaring/lib/libcalendaring_itip.php b/plugins/libcalendaring/lib/libcalendaring_itip.php
index e9be25d..9403f46 100644
--- a/plugins/libcalendaring/lib/libcalendaring_itip.php
+++ b/plugins/libcalendaring/lib/libcalendaring_itip.php
@@ -262,6 +262,7 @@ class libcalendaring_itip
*
* @param array Event object to delegate
* @param mixed Delegatee as string or hash array with keys 'name' and 'mailto'
+ * @param boolean The delegator's RSVP flag
* @return boolean True on success, False on failure
*/
public function delegate_to(&$event, $delegate, $rsvp = false)
@@ -301,6 +302,7 @@ class libcalendaring_itip
$delegate_index = $i;
break;
}
+ // TODO: remove previous delegatee (i.e. attendee that has DELEGATED-FROM == $me)
}
// set/add delegate attendee with RSVP=TRUE and DELEGATED-FROM parameter
@@ -635,7 +637,7 @@ class libcalendaring_itip
// add localized texts for the delegation dialog
if (in_array('delegated', $actions)) {
foreach (array('itipdelegated','itipcomment','delegateinvitation',
- 'delegateto','delegatersvpme','delegateinvalidaddress') as $label) {
+ 'delegateto','delegatersvpme','delegateinvalidaddress','cancel') as $label) {
$this->rc->output->command('add_label', "itip.$label", $this->gettext($label));
}
}
diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js
index bb5a66f..f06b55a 100644
--- a/plugins/libcalendaring/libcalendaring.js
+++ b/plugins/libcalendaring/libcalendaring.js
@@ -916,7 +916,8 @@ rcube_libcalendaring.itip_delegate_dialog = function(callback, selector)
rcm.env.recipients_delimiter = '';
},
close: function(event, ui) {
- rcmail.ksearch_blur();
+ rcm = rcmail.is_framed() ? parent.rcmail : rcmail;
+ rcm.ksearch_blur();
$(this).remove();
}
});
commit 4a150a21390f367f2d68fa8e638b1185fa5c8614
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Thu Nov 6 10:07:54 2014 +0100
Implement iTip delegation functionality for calendar/mail view (#3860)
diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 9b815bc..2b37b79 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -150,6 +150,7 @@ class calendar extends rcube_plugin
$this->register_action('itip-status', array($this, 'event_itip_status'));
$this->register_action('itip-remove', array($this, 'event_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->register_action('resources-list', array($this, 'resources_list'));
$this->register_action('resources-owner', array($this, 'resources_owner'));
$this->register_action('resources-calendar', array($this, 'resources_calendar'));
@@ -239,7 +240,7 @@ class calendar extends rcube_plugin
$this->itip = new calendar_itip($this);
if ($this->rc->config->get('kolab_invitation_calendars'))
- $this->itip->set_rsvp_actions(array('accepted','tentative','declined','needs-action'));
+ $this->itip->set_rsvp_actions(array('accepted','tentative','declined','delegated','needs-action'));
}
return $this->itip;
@@ -957,6 +958,16 @@ class calendar extends rcube_plugin
$ev = $this->driver->get_event($event);
$ev['attendees'] = $event['attendees'];
+
+ // send invitation to delegatee + add it as attendee
+ if ($status == 'delegated' && $event['to']) {
+ $itip = $this->load_itip();
+ if ($itip->delegate_to($ev, $event['to'], (bool)$event['rsvp'])) {
+ $this->rc->output->show_message('calendar.itipsendsuccess', 'confirmation');
+ $noreply = false;
+ }
+ }
+
$event = $ev;
if ($success = $this->driver->edit_rsvp($event, $status)) {
@@ -974,6 +985,7 @@ class calendar extends rcube_plugin
$reply_sender = $attendee['email'];
}
}
+
if (!$noreply) {
$itip = $this->load_itip();
$itip->set_sender_email($reply_sender);
@@ -2567,9 +2579,36 @@ class calendar extends rcube_plugin
$error_msg = $this->gettext('errorimportingevent');
$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 events?
if ($event = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event')) {
+ // 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($event, $delegate, $rsvpme ? true : false)) {
+ $this->rc->output->show_message('calendar.itipsendsuccess', 'confirmation');
+ }
+ else {
+ $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error');
+ }
+
+ // the delegator is set to non-participant, thus save as non-blocking
+ $event['free_busy'] = 'free';
+ }
+
// find writeable calendar to store event
$cal_id = !empty($_REQUEST['_folder']) ? rcube_utils::get_input_value('_folder', rcube_utils::INPUT_POST) : null;
$dontsave = ($_REQUEST['_folder'] === '' && $event['_method'] == 'REQUEST');
@@ -2692,14 +2731,14 @@ class calendar extends rcube_plugin
if ($event['_method'] == 'CANCEL')
$event['status'] = 'CANCELLED';
// show me as free when declined (#1670)
- if ($status == 'declined' || $event['status'] == 'CANCELLED')
+ if ($status == 'declined' || $event['status'] == 'CANCELLED' || $event_attendee['role'] == 'NON-PARTICIPANT')
$event['free_busy'] = 'free';
$success = $this->driver->edit_event($event);
}
else if (!empty($status)) {
$existing['attendees'] = $event['attendees'];
- if ($status == 'declined') // show me as free when declined (#1670)
+ if ($status == 'declined' || $event_attendee['role'] == 'NON-PARTICIPANT') // show me as free when declined (#1670)
$existing['free_busy'] = 'free';
$success = $this->driver->edit_event($existing);
}
@@ -2781,6 +2820,16 @@ class calendar extends rcube_plugin
}
/**
+ * Handler for calendar/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();
+ }
+
+ /**
* Import the full payload from a mail message attachment
*/
public function mail_import_attachment()
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 62b263b..ae745fe 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -2323,20 +2323,41 @@ function rcube_calendar_ui(settings)
}
// when the user accepts or declines an event invitation
- var event_rsvp = function(response)
+ var event_rsvp = function(response, delegate)
{
if (me.selected_event && me.selected_event.attendees && response) {
+ // bring up delegation dialog
+ if (response == 'delegated' && !delegate) {
+ rcube_libcalendaring.itip_delegate_dialog(function(data) {
+ data.rsvp = data.rsvp ? 1 : '';
+ event_rsvp('delegated', data);
+ });
+ return;
+ }
+
// update attendee status
for (var data, i=0; i < me.selected_event.attendees.length; i++) {
data = me.selected_event.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' && status != 'DECLINED')
+ data.role = 'REQ-PARTICIPANT';
+ }
+ }
}
}
// submit status change to server
- var submit_data = $.extend({}, me.selected_event, { source:null, comment:$('#reply-comment-event-rsvp').val() }),
+ var submit_data = $.extend({}, me.selected_event, { source:null, comment:$('#reply-comment-event-rsvp').val() }, (delegate || {})),
noreply = $('#noreply-event-rsvp:checked').length ? 1 : 0;
// import event from mail (temporary iTip event)
@@ -2347,6 +2368,8 @@ function rcube_calendar_ui(settings)
_uid: submit_data._uid,
_part: submit_data._part,
_status: response,
+ _to: (delegate ? delegate.to : null),
+ _rsvp: (delegate && delegate.rsvp) ? 1 : 0,
_noreply: noreply,
_comment: submit_data.comment
});
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
index 722d54f..e01ecd8 100644
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -886,7 +886,7 @@ class calendar_ui
function event_rsvp_buttons($attrib = array())
{
- return $this->cal->itip->itip_rsvp_buttons($attrib, array('accepted','tentative','declined'));
+ return $this->cal->itip->itip_rsvp_buttons($attrib, array('accepted','tentative','declined','delegated'));
}
}
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
index d802153..b63b930 100644
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -190,6 +190,8 @@ $labels['itipmailbodyaccepted'] = "\$sender has accepted the invitation to the f
$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
$labels['itipmailbodydeclined'] = "\$sender has declined the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
$labels['itipmailbodycancel'] = "\$sender has rejected your participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegated'] = "\$sender has delegated the participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender has delegated the participation in the following event to you:\n\n*\$title*\n\nWhen: \$date";
$labels['itipdeclineevent'] = 'Do you want to decline your invitation to this event?';
$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
diff --git a/plugins/libcalendaring/lib/libcalendaring_itip.php b/plugins/libcalendaring/lib/libcalendaring_itip.php
index b2ec3b8..e9be25d 100644
--- a/plugins/libcalendaring/lib/libcalendaring_itip.php
+++ b/plugins/libcalendaring/lib/libcalendaring_itip.php
@@ -30,7 +30,7 @@ class libcalendaring_itip
protected $sender;
protected $domain;
protected $itip_send = false;
- protected $rsvp_actions = array('accepted','tentative','declined');
+ protected $rsvp_actions = array('accepted','tentative','declined','delegated');
protected $rsvp_status = array('accepted','tentative','declined','delegated');
function __construct($plugin, $domain = 'libcalendaring')
@@ -257,6 +257,61 @@ class libcalendaring_itip
return $message;
}
+ /**
+ * Forward the given iTip event as delegation to another person
+ *
+ * @param array Event object to delegate
+ * @param mixed Delegatee as string or hash array with keys 'name' and 'mailto'
+ * @return boolean True on success, False on failure
+ */
+ public function delegate_to(&$event, $delegate, $rsvp = false)
+ {
+ if (is_string($delegate)) {
+ $delegates = rcube_mime::decode_address_list($delegate, 1, false);
+ if (count($delegates) > 0) {
+ $delegate = reset($delegates);
+ }
+ }
+
+ $emails = $this->lib->get_user_emails();
+ $me = $this->rc->user->get_identity();
+
+ // find/create the delegate attendee
+ $delegate_attendee = array(
+ 'email' => $delegate['mailto'],
+ 'name' => $delegate['name'],
+ 'role' => 'REQ-PARTICIPANT',
+ );
+ $delegate_index = count($event['attendees']);
+
+ foreach ($event['attendees'] as $i => $attendee) {
+ // set myself the DELEGATED-TO parameter
+ if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
+ $event['attendees'][$i]['delegated-to'] = $delegate['mailto'];
+ $event['attendees'][$i]['status'] = 'DELEGATED';
+ $event['attendees'][$i]['role'] = 'NON-PARTICIPANT';
+ $event['attendees'][$i]['rsvp'] = $rsvp;
+
+ $me['email'] = $attendee['email'];
+ $delegate_attendee['role'] = $attendee['role'];
+ }
+ // the disired delegatee is already listed as an attendee
+ else if (stripos($delegate['mailto'], $attendee['email']) !== false && $attendee['role'] != 'ORGANIZER') {
+ $delegate_attendee = $attendee;
+ $delegate_index = $i;
+ break;
+ }
+ }
+
+ // set/add delegate attendee with RSVP=TRUE and DELEGATED-FROM parameter
+ $delegate_attendee['rsvp'] = true;
+ $delegate_attendee['status'] = 'NEEDS-ACTION';
+ $delegate_attendee['delegated-from'] = $me['email'];
+ $event['attendees'][$delegate_index] = $delegate_attendee;
+
+ $this->set_sender_email($me['email']);
+ return $this->send_itip_message($event, 'REQUEST', $delegate_attendee, 'itipsubjectdelegatedto', 'itipmailbodydelegatedto');
+ }
/**
* Handler for calendar/itip-status requests
@@ -332,7 +387,7 @@ class libcalendaring_itip
$html = html::div('rsvp-status ' . $status_lc, $this->gettext(array(
'name' => 'attendee' . $status_lc,
'vars' => array(
- 'delegatedto' => Q($attendee['delegated-to'] ?: '?'),
+ 'delegatedto' => Q($event['delegated-to'] ?: ($attendee['delegated-to'] ?: '?')),
)
)));
}
@@ -400,6 +455,8 @@ class libcalendaring_itip
if (!empty($attendee['email']) && $attendee['role'] != 'ORGANIZER') {
$metadata['attendee'] = $attendee['email'];
$rsvp_status = strtoupper($attendee['status']);
+ if ($attendee['delegated-to'])
+ $metadata['delegated-to'] = $attendee['delegated-to'];
break;
}
}
@@ -498,6 +555,11 @@ class libcalendaring_itip
$buttons[] = html::div(array('id' => 'rsvp-'.$dom_id, 'class' => 'rsvp-buttons', 'style' => 'display:none'), $rsvp_buttons);
$buttons[] = html::div(array('id' => 'update-'.$dom_id, 'style' => 'display:none'), $update_button);
+
+ // prepare autocompletion for delegation dialog
+ if (in_array('delegated', $this->rsvp_actions)) {
+ $this->rc->autocomplete_init();
+ }
}
// for CANCEL messages, we can:
else if ($method == 'CANCEL') {
@@ -530,8 +592,6 @@ class libcalendaring_itip
$buttons[] = html::div(array('id' => 'import-'.$dom_id, 'style' => 'display:none'), $import_button);
}
- // TODO: add option/checkbox to delete this message after update
-
// pass some metadata about the event and trigger the asynchronous status check
$metadata['fallback'] = $rsvp_status;
$metadata['rsvp'] = intval($metadata['rsvp']);
@@ -539,7 +599,9 @@ class libcalendaring_itip
$this->rc->output->add_script("rcube_libcalendaring.fetch_itip_object_status(" . json_serialize($metadata) . ")", 'docready');
// get localized texts from the right domain
- foreach (array('savingdata','deleteobjectconfirm','declinedeleteconfirm','declineattendee','declineattendeeconfirm','cancel') as $label) {
+ foreach (array('savingdata','deleteobjectconfirm','declinedeleteconfirm','declineattendee',
+ 'cancel','itipdelegated','declineattendeeconfirm','itipcomment','delegateinvitation',
+ 'delegateto','delegatersvpme','delegateinvalidaddress') as $label) {
$this->rc->output->command('add_label', "itip.$label", $this->gettext($label));
}
@@ -570,6 +632,14 @@ class libcalendaring_itip
$buttons .= html::div('itip-reply-controls', $this->itip_rsvp_options_ui($attrib['id']));
+ // add localized texts for the delegation dialog
+ if (in_array('delegated', $actions)) {
+ foreach (array('itipdelegated','itipcomment','delegateinvitation',
+ 'delegateto','delegatersvpme','delegateinvalidaddress') as $label) {
+ $this->rc->output->command('add_label', "itip.$label", $this->gettext($label));
+ }
+ }
+
return html::div($attrib,
html::div('label', $this->gettext('acceptinvitation')) .
html::div('rsvp-buttons', $buttons));
diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js
index 8bf9c35..bb5a66f 100644
--- a/plugins/libcalendaring/libcalendaring.js
+++ b/plugins/libcalendaring/libcalendaring.js
@@ -803,6 +803,22 @@ rcube_libcalendaring.add_from_itip_mail = function(mime_id, task, status, dom_id
del = confirm(rcmail.gettext('itip.declinedeleteconfirm'));
}
+ // open dialog for iTip delegation
+ if (status == 'delegated') {
+ rcube_libcalendaring.itip_delegate_dialog(function(data) {
+ rcmail.http_post(task + '/itip-delegate', {
+ _uid: rcmail.env.uid,
+ _mbox: rcmail.env.mailbox,
+ _part: mime_id,
+ _to: data.to,
+ _rsvp: data.rsvp ? 1 : 0,
+ _comment: data.comment,
+ _folder: data.target
+ }, rcmail.set_busy(true, 'itip.savingdata'));
+ }, $('#rsvp-'+dom_id+' .folder-select'));
+ return false;
+ }
+
var noreply = 0, comment = '';
if (dom_id) {
noreply = $('#noreply-'+dom_id+':checked').length ? 1 : 0;
@@ -825,6 +841,90 @@ rcube_libcalendaring.add_from_itip_mail = function(mime_id, task, status, dom_id
};
/**
+ * Helper function to render the iTip delegation dialog
+ * and trigger a callback function when submitted.
+ */
+rcube_libcalendaring.itip_delegate_dialog = function(callback, selector)
+{
+ // show dialog for entering the delegatee address and comment
+ var html = '<form class="itip-dialog-form" action="javascript:void()">' +
+ '<div class="form-section">' +
+ '<label for="itip-delegate-to">' + rcmail.gettext('itip.delegateto') + '</label><br/>' +
+ '<input type="text" id="itip-delegate-to" class="text" size="40" value="" />' +
+ '</div>' +
+ '<div class="form-section">' +
+ '<label for="itip-delegate-rsvp">' +
+ '<input type="checkbox" id="itip-delegate-rsvp" class="checkbox" size="40" value="" />' +
+ rcmail.gettext('itip.delegatersvpme') +
+ '</label>' +
+ '</div>' +
+ '<div class="form-section">' +
+ '<textarea id="itip-delegate-comment" class="itip-comment" cols="40" rows="8" placeholder="' +
+ rcmail.gettext('itip.itipcomment') + '"></textarea>' +
+ '</div>' +
+ '<div class="form-section">' +
+ (selector ? selector.html() : '') +
+ '</div>' +
+ '</form>';
+
+ var dialog, buttons = [];
+ buttons.push({
+ text: rcmail.gettext('itipdelegated', 'itip'),
+ click: function() {
+ var doc = window.parent.document,
+ delegatee = String($('#itip-delegate-to', doc).val()).replace(/(^\s+)|(\s+$)/, '');
+
+ if (delegatee != '' && rcube_check_email(delegatee, true)) {
+ callback({
+ to: delegatee,
+ rsvp: $('#itip-delegate-rsvp', doc).prop('checked'),
+ comment: $('#itip-delegate-comment', doc).val(),
+ target: $('#itip-saveto', doc).val()
+ });
+
+ setTimeout(function() { dialog.dialog("close"); }, 500);
+ }
+ else {
+ alert(rcmail.gettext('itip.delegateinvalidaddress'));
+ $('#itip-delegate-to', doc).focus();
+ }
+ }
+ });
+
+ buttons.push({
+ text: rcmail.gettext('cancel', 'itip'),
+ click: function() {
+ dialog.dialog('close');
+ }
+ });
+
+ dialog = rcmail.show_popup_dialog(html, rcmail.gettext('delegateinvitation', 'itip'), buttons, {
+ width: 460,
+ open: function(event, ui) {
+ $(this).parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().addClass('mainaction');
+ $(this).find('#itip-saveto').val('');
+
+ // initialize autocompletion
+ var ac_props, rcm = rcmail.is_framed() ? parent.rcmail : rcmail;
+ if (rcmail.env.autocomplete_threads > 0) {
+ ac_props = {
+ threads: rcmail.env.autocomplete_threads,
+ sources: rcmail.env.autocomplete_sources
+ };
+ }
+ rcm.init_address_input_events($(this).find('#itip-delegate-to').focus(), ac_props);
+ rcm.env.recipients_delimiter = '';
+ },
+ close: function(event, ui) {
+ rcmail.ksearch_blur();
+ $(this).remove();
+ }
+ });
+
+ return dialog;
+};
+
+/**
*
*/
rcube_libcalendaring.remove_from_itip = function(uid, task, title)
@@ -870,7 +970,7 @@ rcube_libcalendaring.decline_attendee_reply = function(mime_id, task)
dialog = rcmail.show_popup_dialog(html, rcmail.gettext('declineattendee', 'itip'), buttons, {
width: 460,
open: function() {
- $(this).parent().find('.ui-button').first().addClass('mainaction');
+ $(this).parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().addClass('mainaction');
$('#itip-decline-comment').focus();
}
});
diff --git a/plugins/libcalendaring/localization/en_US.inc b/plugins/libcalendaring/localization/en_US.inc
index 9c3507c..2542c47 100644
--- a/plugins/libcalendaring/localization/en_US.inc
+++ b/plugins/libcalendaring/localization/en_US.inc
@@ -98,6 +98,8 @@ $labels['itipsubjectdeclined'] = '"$title" has been declined by $name';
$labels['itipsubjectin-process'] = '"$title" is in-process by $name';
$labels['itipsubjectcompleted'] = '"$title" was completed by $name';
$labels['itipsubjectcancel'] = 'Your participation in "$title" has been cancelled';
+$labels['itipsubjectdelegated'] = '"$title" has been delegated by $name';
+$labels['itipsubjectdelegatedto'] = '"$title" has been delegated to you by $name';
$labels['itipnewattendee'] = 'This is a reply from a new participant';
$labels['updateattendeestatus'] = 'Update the participant\'s status';
@@ -139,6 +141,11 @@ $labels['openpreview'] = 'Open Preview';
$labels['deleteobjectconfirm'] = 'Do you really want to delete this object?';
$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined object from your account?';
+$labels['delegateinvitation'] = 'Delegate Invitation';
+$labels['delegateto'] = 'Delegate to';
+$labels['delegatersvpme'] = 'Keep me informed about updates of this incidence';
+$labels['delegateinvalidaddress'] = 'Please enter a valid email address for the delegate';
+
$labels['savingdata'] = 'Saving data...';
// attendees labels
diff --git a/plugins/libcalendaring/skins/larry/libcal.css b/plugins/libcalendaring/skins/larry/libcal.css
index 89e123f..f679abc 100644
--- a/plugins/libcalendaring/skins/larry/libcal.css
+++ b/plugins/libcalendaring/skins/larry/libcal.css
@@ -156,3 +156,11 @@ label.noreply-toggle + a.reply-comment-toggle {
margin-top: -1.4em;
}
+.itip-dialog-form input.text {
+ width: 98%;
+}
+
+.itip-dialog-form label > input.checkbox {
+ margin-left: 0;
+ margin-right: 10px;
+}
commit 17f8ec0d04fe4d22c51f12ac8c9600a344efa16c
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Wed Nov 5 15:17:43 2014 +0100
Align calendar labels with libcalendaring and tasklist plugins
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 912f6ce..62b263b 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -445,7 +445,7 @@ function rcube_calendar_ui(settings)
if (event.status) {
var status_lc = String(event.status).toLowerCase();
- $('#event-status').show().children('.event-text').html(Q(rcmail.gettext(status_lc,'calendar')));
+ $('#event-status').show().children('.event-text').html(Q(rcmail.gettext('status-'+status_lc,'calendar')));
$dialog.addClass('status-'+status_lc);
}
if (event.sensitivity && event.sensitivity != 'public') {
@@ -868,7 +868,7 @@ function rcube_calendar_ui(settings)
};
if (event.id) {
- buttons[rcmail.gettext('remove', 'calendar')] = function() {
+ buttons[rcmail.gettext('delete', 'calendar')] = function() {
me.delete_event(event);
$dialog.dialog('close');
};
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
index 612bda5..722d54f 100644
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -407,8 +407,8 @@ class calendar_ui
$attrib['name'] = 'status';
$select = new html_select($attrib);
$select->add('---', '');
- $select->add($this->cal->gettext('confirmed'), 'CONFIRMED');
- $select->add($this->cal->gettext('cancelled'), 'CANCELLED');
+ $select->add($this->cal->gettext('status-confirmed'), 'CONFIRMED');
+ $select->add($this->cal->gettext('status-cancelled'), 'CANCELLED');
//$select->add($this->cal->gettext('tentative'), 'TENTATIVE');
return $select->show(null);
}
diff --git a/plugins/calendar/localization/ca_ES.inc b/plugins/calendar/localization/ca_ES.inc
index 53c02ec..a49f56a 100644
--- a/plugins/calendar/localization/ca_ES.inc
+++ b/plugins/calendar/localization/ca_ES.inc
@@ -73,8 +73,8 @@ $labels['outofoffice'] = 'Fora de l\'oficina';
$labels['tentative'] = 'Provisional';
$labels['mystatus'] = 'El meu estat';
$labels['status'] = 'Estat';
-$labels['confirmed'] = 'Confirmat';
-$labels['cancelled'] = 'Cancel·lat';
+$labels['status-confirmed'] = 'Confirmat';
+$labels['status-cancelled'] = 'Cancel·lat';
$labels['priority'] = 'Prioritat';
$labels['sensitivity'] = 'Privadesa';
$labels['public'] = 'públic';
diff --git a/plugins/calendar/localization/da_DK.inc b/plugins/calendar/localization/da_DK.inc
index 006d49f..f76df92 100644
--- a/plugins/calendar/localization/da_DK.inc
+++ b/plugins/calendar/localization/da_DK.inc
@@ -62,8 +62,8 @@ $labels['busy'] = 'Optaget';
$labels['outofoffice'] = 'Ikke på kontoret';
$labels['tentative'] = 'Forsøgsvis';
$labels['status'] = 'Status';
-$labels['confirmed'] = 'Bekræftet';
-$labels['cancelled'] = 'Annulleret';
+$labels['status-confirmed'] = 'Bekræftet';
+$labels['status-cancelled'] = 'Annulleret';
$labels['priority'] = 'Prioritet';
$labels['sensitivity'] = 'Privatliv';
$labels['public'] = 'offentlig';
diff --git a/plugins/calendar/localization/de_DE.inc b/plugins/calendar/localization/de_DE.inc
index e68d8b2..9ec5ba4 100644
--- a/plugins/calendar/localization/de_DE.inc
+++ b/plugins/calendar/localization/de_DE.inc
@@ -80,8 +80,8 @@ $labels['outofoffice'] = 'Abwesend';
$labels['tentative'] = 'Mit Vorbehalt';
$labels['mystatus'] = 'Mein Status';
$labels['status'] = 'Status';
-$labels['confirmed'] = 'Bestätigt';
-$labels['cancelled'] = 'Gekündigt';
+$labels['status-confirmed'] = 'Bestätigt';
+$labels['status-cancelled'] = 'Gekündigt';
$labels['priority'] = 'Priorität';
$labels['sensitivity'] = 'Sichtbarkeit';
$labels['public'] = 'öffentlich';
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
index 630a47b..d802153 100644
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -80,8 +80,8 @@ $labels['outofoffice'] = 'Out of Office';
$labels['tentative'] = 'Tentative';
$labels['mystatus'] = 'My status';
$labels['status'] = 'Status';
-$labels['confirmed'] = 'Confirmed';
-$labels['cancelled'] = 'Cancelled';
+$labels['status-confirmed'] = 'Confirmed';
+$labels['status-cancelled'] = 'Cancelled';
$labels['priority'] = 'Priority';
$labels['sensitivity'] = 'Privacy';
$labels['public'] = 'public';
diff --git a/plugins/calendar/localization/es_AR.inc b/plugins/calendar/localization/es_AR.inc
index 353e867..a0d4420 100644
--- a/plugins/calendar/localization/es_AR.inc
+++ b/plugins/calendar/localization/es_AR.inc
@@ -73,8 +73,8 @@ $labels['outofoffice'] = 'Fuera de la oficina';
$labels['tentative'] = 'Tentativo';
$labels['mystatus'] = 'Mi estado';
$labels['status'] = 'Estado';
-$labels['confirmed'] = 'Confirmado';
-$labels['cancelled'] = 'Cancelado';
+$labels['status-confirmed'] = 'Confirmado';
+$labels['status-cancelled'] = 'Cancelado';
$labels['priority'] = 'Prioridad';
$labels['sensitivity'] = 'Privacidad';
$labels['public'] = 'público';
diff --git a/plugins/calendar/localization/fi_FI.inc b/plugins/calendar/localization/fi_FI.inc
index 71eeaad..e473619 100644
--- a/plugins/calendar/localization/fi_FI.inc
+++ b/plugins/calendar/localization/fi_FI.inc
@@ -55,8 +55,8 @@ $labels['freebusy'] = 'Aseta tilakseni';
$labels['free'] = 'Vapaa';
$labels['busy'] = 'Varattu';
$labels['status'] = 'Tila';
-$labels['confirmed'] = 'Vahvistettu';
-$labels['cancelled'] = 'Peruttu';
+$labels['status-confirmed'] = 'Vahvistettu';
+$labels['status-cancelled'] = 'Peruttu';
$labels['priority'] = 'Tärkeys';
$labels['sensitivity'] = 'Yksityisyys';
$labels['public'] = 'julkinen';
diff --git a/plugins/calendar/localization/fr_FR.inc b/plugins/calendar/localization/fr_FR.inc
index 31fed82..c096375 100644
--- a/plugins/calendar/localization/fr_FR.inc
+++ b/plugins/calendar/localization/fr_FR.inc
@@ -61,8 +61,8 @@ $labels['busy'] = 'Occupé';
$labels['outofoffice'] = 'Absent';
$labels['tentative'] = 'Provisoire';
$labels['status'] = 'Statut';
-$labels['confirmed'] = 'Confirmé';
-$labels['cancelled'] = 'Annulé';
+$labels['status-confirmed'] = 'Confirmé';
+$labels['status-cancelled'] = 'Annulé';
$labels['priority'] = 'Priorité';
$labels['sensitivity'] = 'Diffusion';
$labels['public'] = 'publique';
diff --git a/plugins/calendar/localization/hu_HU.inc b/plugins/calendar/localization/hu_HU.inc
index 7496362..7ba7c0b 100644
--- a/plugins/calendar/localization/hu_HU.inc
+++ b/plugins/calendar/localization/hu_HU.inc
@@ -63,8 +63,8 @@ $labels['busy'] = 'Foglalt';
$labels['outofoffice'] = 'Házon kÃvűl';
$labels['tentative'] = 'Feltételes';
$labels['status'] = 'Stát.';
-$labels['confirmed'] = 'Confirmed';
-$labels['cancelled'] = 'Cancelled';
+$labels['status-confirmed'] = 'Confirmed';
+$labels['status-cancelled'] = 'Cancelled';
$labels['priority'] = 'Prioritás';
$labels['sensitivity'] = 'Manánszféra';
$labels['public'] = 'publikus';
diff --git a/plugins/calendar/localization/nl_NL.inc b/plugins/calendar/localization/nl_NL.inc
index 4f18e21..9c0c331 100644
--- a/plugins/calendar/localization/nl_NL.inc
+++ b/plugins/calendar/localization/nl_NL.inc
@@ -63,8 +63,8 @@ $labels['busy'] = 'Bezet';
$labels['outofoffice'] = 'Niet Aanwezig';
$labels['tentative'] = 'Misschien';
$labels['status'] = 'Status';
-$labels['confirmed'] = 'Bevestigd';
-$labels['cancelled'] = 'Afgelast';
+$labels['status-confirmed'] = 'Bevestigd';
+$labels['status-cancelled'] = 'Afgelast';
$labels['priority'] = 'Prioriteit';
$labels['sensitivity'] = 'Zichtbaarheid';
$labels['public'] = 'publiek';
diff --git a/plugins/calendar/localization/pl_PL.inc b/plugins/calendar/localization/pl_PL.inc
index 6df3d20..8bae25a 100644
--- a/plugins/calendar/localization/pl_PL.inc
+++ b/plugins/calendar/localization/pl_PL.inc
@@ -73,8 +73,8 @@ $labels['outofoffice'] = 'Poza biurem';
$labels['tentative'] = 'Niepewny';
$labels['mystatus'] = 'Mój status';
$labels['status'] = 'Status';
-$labels['confirmed'] = 'Potwierdzony';
-$labels['cancelled'] = 'Anulowany';
+$labels['status-confirmed'] = 'Potwierdzony';
+$labels['status-cancelled'] = 'Anulowany';
$labels['priority'] = 'Priorytet';
$labels['sensitivity'] = 'PoufnoÅÄ';
$labels['public'] = 'publiczny';
diff --git a/plugins/calendar/localization/pt_BR.inc b/plugins/calendar/localization/pt_BR.inc
index 78650fb..a959a72 100644
--- a/plugins/calendar/localization/pt_BR.inc
+++ b/plugins/calendar/localization/pt_BR.inc
@@ -63,8 +63,8 @@ $labels['busy'] = 'Ocupado';
$labels['outofoffice'] = 'Fora de escritório';
$labels['tentative'] = 'Tentativa';
$labels['status'] = 'Situação';
-$labels['confirmed'] = 'Confirmado';
-$labels['cancelled'] = 'Cancalado';
+$labels['status-confirmed'] = 'Confirmado';
+$labels['status-cancelled'] = 'Cancalado';
$labels['priority'] = 'Prioridade';
$labels['sensitivity'] = 'Privacidade';
$labels['public'] = 'público';
diff --git a/plugins/calendar/localization/ru_RU.inc b/plugins/calendar/localization/ru_RU.inc
index 84ae137..b15f32e 100644
--- a/plugins/calendar/localization/ru_RU.inc
+++ b/plugins/calendar/localization/ru_RU.inc
@@ -73,8 +73,8 @@ $labels['outofoffice'] = 'Ðне оÑиÑа';
$labels['tentative'] = 'ÐеопÑеделÑнно';
$labels['mystatus'] = 'Ðой ÑÑаÑÑÑ';
$labels['status'] = 'СÑаÑÑÑ';
-$labels['confirmed'] = 'ÐодÑвеждÑннÑй';
-$labels['cancelled'] = 'ÐÑмененнÑй';
+$labels['status-confirmed'] = 'ÐодÑвеждÑннÑй';
+$labels['status-cancelled'] = 'ÐÑмененнÑй';
$labels['priority'] = 'ÐÑиоÑиÑеÑ';
$labels['sensitivity'] = 'СекÑеÑноÑÑÑ';
$labels['public'] = 'обÑедоÑÑÑпнаÑ';
More information about the commits
mailing list