summaryrefslogtreecommitdiff
path: root/plugins/libcalendaring
diff options
context:
space:
mode:
authorThomas Bruederli <bruederli@kolabsys.com>2014-11-06 09:07:54 (GMT)
committerThomas Bruederli <bruederli@kolabsys.com>2014-11-06 16:09:59 (GMT)
commit4a150a21390f367f2d68fa8e638b1185fa5c8614 (patch)
tree427348b95b9e5c45a5a0a70413a40432e4863044 /plugins/libcalendaring
parent17f8ec0d04fe4d22c51f12ac8c9600a344efa16c (diff)
downloadroundcubemail-plugins-kolab-4a150a21390f367f2d68fa8e638b1185fa5c8614.tar.gz
Implement iTip delegation functionality for calendar/mail view (#3860)
Diffstat (limited to 'plugins/libcalendaring')
-rw-r--r--plugins/libcalendaring/lib/libcalendaring_itip.php80
-rw-r--r--plugins/libcalendaring/libcalendaring.js102
-rw-r--r--plugins/libcalendaring/localization/en_US.inc7
-rw-r--r--plugins/libcalendaring/skins/larry/libcal.css8
4 files changed, 191 insertions, 6 deletions
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;
+}