T1523: Support shared calendars in iTip handling

This commit is contained in:
Aleksander Machniak 2016-10-03 14:10:57 +02:00
parent 12cf79adaa
commit 4a9e42a026
6 changed files with 118 additions and 66 deletions

View file

@ -2521,12 +2521,22 @@ class calendar extends rcube_plugin
/** /**
* Find an event in user calendars * Find an event in user calendars
*/ */
protected function find_event($event) protected function find_event($event, &$mode)
{ {
$this->load_driver(); $this->load_driver();
// We search for writeable calendars in personal namespace by default // We search for writeable calendars in personal namespace by default
return $this->driver->get_event($event, calendar_driver::FILTER_WRITEABLE | calendar_driver::FILTER_PERSONAL); $mode = calendar_driver::FILTER_WRITEABLE | calendar_driver::FILTER_PERSONAL;
$result = $this->driver->get_event($event, $mode);
// ... now check shared folders if not found
if (!$result) {
$result = $this->driver->get_event($event, calendar_driver::FILTER_WRITEABLE | calendar_driver::FILTER_SHARED);
if ($result) {
$mode |= calendar_driver::FILTER_SHARED;
}
}
return $result;
} }
/** /**
@ -2538,14 +2548,18 @@ class calendar extends rcube_plugin
$this->load_driver(); $this->load_driver();
// find local copy of the referenced event // find local copy of the referenced event (in personal namespace)
$existing = $this->find_event($data); $existing = $this->find_event($data, $mode);
$itip = $this->load_itip(); $is_shared = $mode & calendar_driver::FILTER_SHARED;
$response = $itip->get_itip_status($data, $existing); $itip = $this->load_itip();
$response = $itip->get_itip_status($data, $existing);
// get a list of writeable calendars to save new events to // get a list of writeable calendars to save new events to
if (!$existing && !$data['nosave'] && $response['action'] == 'rsvp' || $response['action'] == 'import') { if ((!$existing || $is_shared)
$calendars = $this->driver->list_calendars(calendar_driver::FILTER_WRITEABLE | calendar_driver::FILTER_PERSONAL); && !$data['nosave']
&& ($response['action'] == 'rsvp' || $response['action'] == 'import')
) {
$calendars = $this->driver->list_calendars($mode);
$calendar_select = new html_select(array('name' => 'calendar', 'id' => 'itip-saveto', 'is_escaped' => true)); $calendar_select = new html_select(array('name' => 'calendar', 'id' => 'itip-saveto', 'is_escaped' => true));
$calendar_select->add('--', ''); $calendar_select->add('--', '');
$numcals = 0; $numcals = 0;
@ -2562,7 +2576,7 @@ class calendar extends rcube_plugin
if ($calendar_select) { if ($calendar_select) {
$default_calendar = $this->get_default_calendar($data['sensitivity'], $calendars); $default_calendar = $this->get_default_calendar($data['sensitivity'], $calendars);
$response['select'] = html::span('folder-select', $this->gettext('saveincalendar') . ' ' . $response['select'] = html::span('folder-select', $this->gettext('saveincalendar') . ' ' .
$calendar_select->show($default_calendar['id'])); $calendar_select->show($is_shared ? $existing['calendar'] : $default_calendar['id']));
} }
else if ($data['nosave']) { else if ($data['nosave']) {
$response['select'] = html::tag('input', array('type' => 'hidden', 'name' => 'calendar', 'id' => 'itip-saveto', 'value' => '')); $response['select'] = html::tag('input', array('type' => 'hidden', 'name' => 'calendar', 'id' => 'itip-saveto', 'value' => ''));
@ -2884,15 +2898,19 @@ class calendar extends rcube_plugin
$event['free_busy'] = 'free'; $event['free_busy'] = 'free';
} }
$mode = calendar_driver::FILTER_PERSONAL
| calendar_driver::FILTER_SHARED
| calendar_driver::FILTER_WRITEABLE;
// find writeable calendar to store event // find writeable calendar to store event
$cal_id = !empty($_REQUEST['_folder']) ? rcube_utils::get_input_value('_folder', rcube_utils::INPUT_POST) : null; $cal_id = rcube_utils::get_input_value('_folder', rcube_utils::INPUT_POST);
$dontsave = ($_REQUEST['_folder'] === '' && $event['_method'] == 'REQUEST'); $dontsave = $cal_id === '' && $event['_method'] == 'REQUEST';
$calendars = $this->driver->list_calendars(calendar_driver::FILTER_PERSONAL); $calendars = $this->driver->list_calendars($mode);
$calendar = $calendars[$cal_id]; $calendar = $calendars[$cal_id];
// select default calendar except user explicitly selected 'none' // select default calendar except user explicitly selected 'none'
if (!$calendar && !$dontsave) if (!$calendar && !$dontsave)
$calendar = $this->get_default_calendar($event['sensitivity']); $calendar = $this->get_default_calendar($event['sensitivity'], $calendars);
$metadata = array( $metadata = array(
'uid' => $event['uid'], 'uid' => $event['uid'],
@ -2940,9 +2958,16 @@ class calendar extends rcube_plugin
// save to calendar // save to calendar
if ($calendar && $calendar['editable']) { if ($calendar && $calendar['editable']) {
// check for existing event with the same UID // check for existing event with the same UID
$existing = $this->find_event($event); $existing = $this->find_event($event, $mode);
// we'll create a new copy if user decided to change the calendar
if ($existing && $cal_id && $calendar && $calendar['id'] != $existing['calendar']) {
$existing = null;
}
if ($existing) { if ($existing) {
$calendar = $calendars[$existing['calendar']];
// forward savemode for correct updates of recurring events // forward savemode for correct updates of recurring events
$existing['_savemode'] = $savemode ?: $event['_savemode']; $existing['_savemode'] = $savemode ?: $event['_savemode'];
@ -3015,10 +3040,9 @@ class calendar extends rcube_plugin
$event['id'] = $existing['id']; $event['id'] = $existing['id'];
$event['calendar'] = $existing['calendar']; $event['calendar'] = $existing['calendar'];
// preserve my participant status for regular updates // merge attendees status
if (empty($status)) { // e.g. preserve my participant status for regular updates
$this->lib->merge_attendees($event, $existing); $this->lib->merge_attendees($event, $existing, $status);
}
// set status=CANCELLED on CANCEL messages // set status=CANCELLED on CANCEL messages
if ($event['_method'] == 'CANCEL') if ($event['_method'] == 'CANCEL')

View file

@ -101,6 +101,7 @@ abstract class calendar_driver
const FILTER_PERSONAL = 8; const FILTER_PERSONAL = 8;
const FILTER_PRIVATE = 16; const FILTER_PRIVATE = 16;
const FILTER_CONFIDENTIAL = 32; const FILTER_CONFIDENTIAL = 32;
const FILTER_SHARED = 64;
const BIRTHDAY_CALENDAR_ID = '__bdays__'; const BIRTHDAY_CALENDAR_ID = '__bdays__';
// features supported by backend // features supported by backend

View file

@ -296,16 +296,15 @@ class kolab_driver extends calendar_driver
'list' => $this->calendars, 'list' => $this->calendars,
'calendars' => $calendars, 'calendars' => $calendars,
'filter' => $filter, 'filter' => $filter,
'editable' => ($filter & self::FILTER_WRITEABLE),
'insert' => ($filter & self::FILTER_INSERTABLE),
'active' => ($filter & self::FILTER_ACTIVE),
'personal' => ($filter & self::FILTER_PERSONAL)
)); ));
if ($plugin['abort']) { if ($plugin['abort']) {
return $plugin['calendars']; return $plugin['calendars'];
} }
$personal = $filter & self::FILTER_PERSONAL;
$shared = $filter & self::FILTER_SHARED;
foreach ($this->calendars as $cal) { foreach ($this->calendars as $cal) {
if (!$cal->ready) { if (!$cal->ready) {
continue; continue;
@ -325,9 +324,13 @@ class kolab_driver extends calendar_driver
if (($filter & self::FILTER_CONFIDENTIAL) && $cal->subtype != 'confidential') { if (($filter & self::FILTER_CONFIDENTIAL) && $cal->subtype != 'confidential') {
continue; continue;
} }
if (($filter & self::FILTER_PERSONAL) && $cal->get_namespace() != 'personal') { if ($personal || $shared) {
continue; $ns = $cal->get_namespace();
if (!(($personal && $ns == 'personal') || ($shared && $ns == 'shared'))) {
continue;
}
} }
$calendars[$cal->id] = $cal; $calendars[$cal->id] = $cal;
} }

View file

@ -783,10 +783,19 @@ class kolab_delegation_engine
*/ */
public function delegator_folder_filter(&$args) public function delegator_folder_filter(&$args)
{ {
$context = $this->delegator_context(); $context = $this->delegator_context();
if (empty($context)) {
return $args;
}
$storage = $this->rc->get_storage(); $storage = $this->rc->get_storage();
$other_ns = $storage->get_namespace('other'); $other_ns = $storage->get_namespace('other');
$delim = $storage->get_hierarchy_delimiter(); $delim = $storage->get_hierarchy_delimiter();
$editable = $filter & calendar_driver::FILTER_WRITEABLE;
$active = $filter & calendar_driver::FILTER_ACTIVE;
$personal = $filter & calendar_driver::FILTER_PERSONAL;
$shared = $filter & calendar_driver::FILTER_SHARED;
$calendars = array(); $calendars = array();
// code parts derived from kolab_driver::filter_calendars() // code parts derived from kolab_driver::filter_calendars()
@ -794,25 +803,19 @@ class kolab_delegation_engine
if (!$cal->ready) { if (!$cal->ready) {
continue; continue;
} }
if ($args['writeable'] && $cal->readonly) { if ($editable && !$cal->editable) {
continue; continue;
} }
if ($args['active'] && !$cal->storage->is_active()) { if ($active && !$cal->storage->is_active()) {
continue; continue;
} }
if ($args['personal']) { if ($personal || $shared) {
$ns = $cal->get_namespace(); $ns = $cal->get_namespace();
if (empty($context)) { if ($personal && $ns == 'personal') {
if ($ns != 'personal') { continue;
continue;
}
} }
else { else if ($personal && $ns == 'other') {
if ($ns != 'other') {
continue;
}
$found = false; $found = false;
foreach ($other_ns as $ns) { foreach ($other_ns as $ns) {
$folder = $ns[0] . $context . $delim; $folder = $ns[0] . $context . $delim;
@ -825,6 +828,9 @@ class kolab_delegation_engine
continue; continue;
} }
} }
else if (!$shared || $ns != 'shared') {
continue;
}
} }
$calendars[$cal->id] = $cal; $calendars[$cal->id] = $cal;

View file

@ -406,8 +406,8 @@ class libcalendaring_itip
else if (!$existing && !$rsvp) { else if (!$existing && !$rsvp) {
$action = 'import'; $action = 'import';
} }
else if ($latest && $status_lc != 'needs-action') { else if ($status_lc != 'needs-action') {
$action = 'update'; $action = !$latest ? 'update' : '';
} }
$html = html::div('rsvp-status ' . $status_lc, $status_text); $html = html::div('rsvp-status ' . $status_lc, $status_text);

View file

@ -1565,43 +1565,61 @@ class libcalendaring extends rcube_plugin
* Merge attendees of the old and new event version * Merge attendees of the old and new event version
* with keeping current user and his delegatees status * with keeping current user and his delegatees status
* *
* @param array &$new New object data * @param array &$new New object data
* @param array $old Old object data * @param array $old Old object data
* @param bool $status New status of the current user
*/ */
public function merge_attendees(&$new, $old) public function merge_attendees(&$new, $old, $status = null)
{ {
$emails = $this->get_user_emails(); if (empty($status)) {
$delegates = array(); $emails = $this->get_user_emails();
$attendees = array(); $delegates = array();
$attendees = array();
// keep attendee status of the current user // keep attendee status of the current user
foreach ((array) $new['attendees'] as $i => $attendee) { foreach ((array) $new['attendees'] as $i => $attendee) {
if (empty($attendee['email'])) { if (empty($attendee['email'])) {
continue; continue;
}
$attendees[] = $email = strtolower($attendee['email']);
if (in_array($email, $emails)) {
foreach ($old['attendees'] as $_attendee) {
if ($attendee['email'] == $_attendee['email']) {
$new['attendees'][$i] = $_attendee;
if ($_attendee['status'] == 'DELEGATED' && ($email = $_attendee['delegated-to'])) {
$delegates[] = strtolower($email);
}
break;
}
}
}
} }
$attendees[] = $email = strtolower($attendee['email']); // make sure delegated attendee is not lost
foreach ($delegates as $delegatee) {
if (in_array($email, $emails)) { if (!in_array($delegatee, $attendees)) {
foreach ($old['attendees'] as $_attendee) { foreach ((array) $old['attendees'] as $attendee) {
if ($attendee['email'] == $_attendee['email']) { if ($attendee['email'] && ($email = strtolower($attendee['email'])) && $email == $delegatee) {
$new['attendees'][$i] = $_attendee; $new['attendees'][] = $attendee;
if ($_attendee['status'] == 'DELEGATED' && ($email = $_attendee['delegated-to'])) { break;
$delegates[] = strtolower($email);
} }
break;
} }
} }
} }
} }
// make sure delegated attendee is not lost // We also make sure that status of any attendee
foreach ($delegates as $delegatee) { // is not overriden by NEEDS-ACTION if it was already set
if (!in_array($delegatee, $attendees)) { // which could happen if you work with shared events
foreach ((array) $old['attendees'] as $attendee) { foreach ((array) $new['attendees'] as $i => $attendee) {
if ($attendee['email'] && ($email = strtolower($attendee['email'])) && $email == $delegatee) { if ($attendee['email'] && $attendee['status'] == 'NEEDS-ACTION') {
$new['attendees'][] = $attendee; foreach ($old['attendees'] as $_attendee) {
if ($attendee['email'] == $_attendee['email']) {
$new['attendees'][$i]['status'] = $_attendee['status'];
unset($new['attendees'][$i]['rsvp']);
break; break;
} }
} }