mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-01-27 00:09:13 +01:00
- fixed ORGANIZER/ATTENDEE in iCal for CalDAV:
+ do NOT use ORGANIZER for events without further participants or a different organizer + do not include event owner/ORGANIZER as participant in his own calendar, if he is only participant --> all other cases include ORGANZIER and additional as ATTENDEE (tested with iCal on iOS and OS X) - implemented schedule-tag and If-Schedule-Tag-Match header from CalDAV Scheduling - allow to change participant status and add/remove alarms with schedule-tag instead of ETag --> If-Schedule-Tag-Match header has precedence over If-Match (ETag) header, but limits changes to participant status and alarms --> ToDo: test accepting, rejecting recurrences
This commit is contained in:
parent
e0690d2342
commit
8096c34bef
@ -1915,10 +1915,11 @@ class calendar_bo
|
||||
* Get the etag for an entry
|
||||
*
|
||||
* @param array|int|string $event array with event or cal_id, or cal_id:recur_date for virtual exceptions
|
||||
* @param string &$schedule_tag=null on return schedule-tag (egw_cal.cal_id:egw_cal.cal_etag, no participant modifications!)
|
||||
* @param boolean $client_share_uid_excpetions Does client understand exceptions to be included in VCALENDAR component of series master sharing its UID
|
||||
* @return string|boolean string with etag or false
|
||||
*/
|
||||
function get_etag($entry,$client_share_uid_excpetions=true)
|
||||
function get_etag($entry, &$schedule_tag=null, $client_share_uid_excpetions=true)
|
||||
{
|
||||
if (!is_array($entry))
|
||||
{
|
||||
@ -1926,7 +1927,7 @@ class calendar_bo
|
||||
if (!$this->check_perms(EGW_ACL_FREEBUSY, $entry, 0, 'server')) return false;
|
||||
$entry = $this->read($entry, $recur_date, true, 'server');
|
||||
}
|
||||
$etag = $entry['id'].':'.$entry['etag'];
|
||||
$etag = $schedule_tag = $entry['id'].':'.$entry['etag'];
|
||||
|
||||
// use new MAX(modification date) of egw_cal_user table (deals with virtual exceptions too)
|
||||
if (isset($entry['max_user_modified']))
|
||||
@ -1955,7 +1956,7 @@ class calendar_bo
|
||||
{
|
||||
if ($recurrence['reference'] && $recurrence['id'] != $entry['id']) // ignore series master
|
||||
{
|
||||
$etag .= ':'.$this->get_etag($recurrence);
|
||||
$etag .= ':'.$this->get_etag($recurrence, $full_etag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,18 +238,31 @@ class calendar_groupdav extends groupdav_handler
|
||||
foreach($events as $event)
|
||||
{
|
||||
$event['max_user_modified'] = $max_user_modified[$event['id']];
|
||||
$etag = $this->get_etag($event, $schedule_tag);
|
||||
//header('X-EGROUPWARE-EVENT-'.$event['id'].': '.$event['title'].': '.date('Y-m-d H:i:s',$event['start']).' - '.date('Y-m-d H:i:s',$event['end']));
|
||||
$props = array(
|
||||
'getcontenttype' => HTTP_WebDAV_Server::mkprop('getcontenttype', $this->agent != 'kde' ?
|
||||
'text/calendar; charset=utf-8; component=VEVENT' : 'text/calendar'),
|
||||
'getcontenttype' => $this->agent != 'kde' ? 'text/calendar; charset=utf-8; component=VEVENT' : 'text/calendar',
|
||||
'getetag' => '"'.$etag.'"',
|
||||
'schedule-tag' => HTTP_WebDAV_Server::mkprop(groupdav::CALDAV, 'schedule-tag', '"'.$schedule_tag.'"'),
|
||||
);
|
||||
//error_log(__FILE__ . __METHOD__ . "Calendar Data : $calendar_data");
|
||||
if ($calendar_data)
|
||||
{
|
||||
$content = $this->iCal($event, $filter['users'], strpos($path, '/inbox/') !== false ? 'PUBLISH' : null);
|
||||
$props['getcontentlength'] = bytes($content);
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-data',$content);
|
||||
$props['calendar-data'] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-data',$content);
|
||||
}
|
||||
/* Calendarserver reports new events with schedule-changes: action: create, which iCal request
|
||||
* adding it, unfortunately does not lead to showing the new event in the users inbox
|
||||
if (strpos($path, '/inbox/') !== false && $this->groupdav->prop_requested('schedule-changes'))
|
||||
{
|
||||
$props['schedule-changes'] = HTTP_WebDAV_Server::mkprop(groupdav::CALENDARSERVER,'schedule-changes',array(
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::CALENDARSERVER,'dtstamp',gmdate('Ymd\THis',$event['created']).'Z'),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::CALENDARSERVER,'action',array(
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::CALENDARSERVER,'create',''),
|
||||
)),
|
||||
));
|
||||
}*/
|
||||
$files[] = $this->add_resource($path, $event, $props);
|
||||
}
|
||||
}
|
||||
@ -393,7 +406,9 @@ class calendar_groupdav extends groupdav_handler
|
||||
$options['data'] = $this->iCal($event, $user, strpos($options['path'], '/inbox/') !== false ? 'PUBLISH' : null);
|
||||
$options['mimetype'] = 'text/calendar; charset=utf-8';
|
||||
header('Content-Encoding: identity');
|
||||
header('ETag: "'.$this->get_etag($event).'"');
|
||||
header('ETag: "'.$this->get_etag($event, $schedule_tag).'"');
|
||||
header('Schedule-Tag: "'.$schedule_tag.'"');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -516,7 +531,8 @@ class calendar_groupdav extends groupdav_handler
|
||||
if (!$prefix) $user = null; // /infolog/ does not imply setting the current user (for new entries it's done anyway)
|
||||
|
||||
$return_no_access = true; // as handled by importVCal anyway and allows it to set the status for participants
|
||||
$oldEvent = $this->_common_get_put_delete('PUT',$options,$id,$return_no_access);
|
||||
$oldEvent = $this->_common_get_put_delete('PUT',$options,$id,$return_no_access,
|
||||
isset($_SERVER['HTTP_IF_SCHEDULE_TAG_MATCH'])); // dont fail with 412 Precondition Failed in that case
|
||||
if (!is_null($oldEvent) && !is_array($oldEvent))
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__.': '.print_r($oldEvent,true).function_backtrace());
|
||||
@ -556,6 +572,39 @@ class calendar_groupdav extends groupdav_handler
|
||||
if (is_array($oldEvent))
|
||||
{
|
||||
$eventId = $oldEvent['id'];
|
||||
|
||||
// client specified a CalDAV Scheduling schedule-tag precondition
|
||||
if (isset($_SERVER['HTTP_IF_SCHEDULE_TAG_MATCH']))
|
||||
{
|
||||
$schedule_tag_match = $_SERVER['HTTP_IF_SCHEDULE_TAG_MATCH'];
|
||||
if ($schedule_tag_match[0] == '"') $schedule_tag_match = substr($schedule_tag_match, 1, -1);
|
||||
$this->get_etag($oldEvent, $schedule_tag);
|
||||
|
||||
if ($schedule_tag_match !== $schedule_tag)
|
||||
{
|
||||
return '412 Precondition Failed';
|
||||
}
|
||||
// update only participant status and alarms of current user
|
||||
if (($events = $handler->icaltoegw($vCalendar)))
|
||||
{
|
||||
// todo check behavior for recuring events
|
||||
foreach($events as $event)
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."(, $id, $user, '$prefix') eventId=$eventId, user=$user, event=".array2string($event));
|
||||
if ($event['participants'][$user] != $oldEvent['participants'][$user] &&
|
||||
!$this->bo->set_status($eventId, $user, $event['participants'][$user], $event['recurrence']))
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."(,,$user) failed to set_status($eventId, $user, '{$event['participants'][$user]}')");
|
||||
return '403 Forbidden';
|
||||
}
|
||||
if ($this->debug) error_log(__METHOD__."() set_status($eventId, $user, ".array2string($event['participants'][$user])." , $event[recurrence])");
|
||||
// import alarms
|
||||
$this->sync_alarms($eventId, (array)$event['alarm'], (array)$oldEvent['alarm'], $user, $event['start']);
|
||||
}
|
||||
header('Schedule-Tag: "'.$schedule_tag.'"');
|
||||
return '204 No Content';
|
||||
}
|
||||
}
|
||||
if ($return_no_access)
|
||||
{
|
||||
$retval = true;
|
||||
@ -599,8 +648,10 @@ class calendar_groupdav extends groupdav_handler
|
||||
}
|
||||
}
|
||||
|
||||
$etag = $this->get_etag($cal_id, $schedule_tag);
|
||||
// we should not return an etag here, as we never store the PUT ical byte-by-byte
|
||||
//header('ETag: "'.$this->get_etag($cal_id).'"');
|
||||
//header('ETag: "'.$etag.'"');
|
||||
header('Schedule-Tag: "'.$schedule_tag.'"');
|
||||
|
||||
// send GroupDAV Location header only if we dont use caldav_name as path-attribute
|
||||
if ($retval !== true && self::$path_attr != 'caldav_name')
|
||||
@ -612,6 +663,49 @@ class calendar_groupdav extends groupdav_handler
|
||||
return $retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync alarms of current user: add alarms added on client and remove the ones removed
|
||||
*
|
||||
* @param int $cal_id of event to set alarms
|
||||
* @param array $alarms
|
||||
* @param array $old_alarms
|
||||
* @param int $user account_id of user to create alarm for
|
||||
* @param int $start start-time of event
|
||||
* @ToDo store other alarm properties like: ACTION, DESCRIPTION, X-WR-ALARMUID
|
||||
*/
|
||||
private function sync_alarms($cal_id, array $alarms, array $old_alarms, $user, $start)
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."($cal_id, ".array2string($alarms).', '.array2string($old_alarms).", $user, $start)");
|
||||
// todo import alarms
|
||||
foreach($alarms as $alarm)
|
||||
{
|
||||
if ($alarm['owner'] != $this->user) continue; // only import alarms of current user
|
||||
|
||||
// check if alarm is already stored or from other users
|
||||
foreach($old_alarms as $id => $old_alarm)
|
||||
{
|
||||
if ($old_alarm['owner'] != $user || $alarm['offset'] == $old_alarm['offset'])
|
||||
{
|
||||
unset($old_alarms[$id]); // remove alarms of other user, or already existing alarms
|
||||
}
|
||||
}
|
||||
// alarm not found --> add it
|
||||
if ($alarm['offset'] != $old_alarm['offset'] || $old_alarm['owner'] != $user)
|
||||
{
|
||||
$alarm['owner'] = $user;
|
||||
$alarm['time'] = $start - $alarm['offset'];
|
||||
if ($this->debug) error_log(__METHOD__."() adding new alarm from client ".array2string($alarm));
|
||||
$this->bo->save_alarm($cal_id, $alarm);
|
||||
}
|
||||
}
|
||||
// remove all old alarms left from current user
|
||||
foreach($old_alarms as $id => $old_alarm)
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."() deleting alarm '$id' deleted on client ".array2string($old_alarm));
|
||||
$this->bo->delete_alarm($id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle post request for a schedule entry
|
||||
*
|
||||
@ -982,14 +1076,14 @@ class calendar_groupdav extends groupdav_handler
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the etag for an entry, reimplemented to include the participants and stati in the etag
|
||||
* Get the etag for an entry
|
||||
*
|
||||
* @param array/int $event array with event or cal_id
|
||||
* @return string/boolean string with etag or false
|
||||
* @param array|int $event array with event or cal_id
|
||||
* @return string|boolean string with etag or false
|
||||
*/
|
||||
function get_etag($entry)
|
||||
function get_etag($entry, &$schedule_tag=null)
|
||||
{
|
||||
$etag = $this->bo->get_etag($entry,$this->client_shared_uid_exceptions);
|
||||
$etag = $this->bo->get_etag($entry, $schedule_tag, $this->client_shared_uid_exceptions);
|
||||
|
||||
//error_log(__METHOD__ . "($entry[id] ($entry[etag]): $entry[title] --> etag=$etag");
|
||||
return $etag;
|
||||
@ -999,7 +1093,7 @@ class calendar_groupdav extends groupdav_handler
|
||||
* Check if user has the neccessary rights on an event
|
||||
*
|
||||
* @param int $acl EGW_ACL_READ, EGW_ACL_EDIT or EGW_ACL_DELETE
|
||||
* @param array/int $event event-array or id
|
||||
* @param array|int $event event-array or id
|
||||
* @return boolean null if entry does not exist, false if no access, true if access permitted
|
||||
*/
|
||||
function check_access($acl,$event)
|
||||
|
@ -423,13 +423,14 @@ class calendar_ical extends calendar_boupdate
|
||||
switch ($icalFieldName)
|
||||
{
|
||||
case 'ATTENDEE':
|
||||
$attendees = count($event['participants']);
|
||||
foreach ((array)$event['participants'] as $uid => $status)
|
||||
{
|
||||
calendar_so::split_status($status, $quantity, $role);
|
||||
if ($attendees == 1 &&
|
||||
$uid == $this->user && $status == 'A') continue;
|
||||
// do not include event owner/ORGANIZER as participant in his own calendar, if he is only participant
|
||||
if (count($event['participants']) == 1 && $event['owner'] == $uid) continue;
|
||||
|
||||
if (!($info = $this->resource_info($uid))) continue;
|
||||
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
|
||||
@ -521,7 +522,6 @@ class calendar_ical extends calendar_boupdate
|
||||
break;
|
||||
|
||||
case 'ORGANIZER':
|
||||
// according to iCalendar standard, ORGANIZER not used for events in the own calendar
|
||||
if (!$organizerCN)
|
||||
{
|
||||
$organizerCN = '"' . trim($GLOBALS['egw']->accounts->id2name($event['owner'],'account_firstname')
|
||||
@ -551,7 +551,8 @@ class calendar_ical extends calendar_boupdate
|
||||
$parameters['ATTENDEE'][] = $options;
|
||||
}
|
||||
}
|
||||
if ($this->productManufacturer != 'groupdav' || !$this->check_perms(EGW_ACL_EDIT,$event))
|
||||
// do NOT use ORGANIZER for events without further participants or a different organizer
|
||||
if (count($event['participants']) > 1 || !isset($event['participants'][$event['owner']]))
|
||||
{
|
||||
$attributes['ORGANIZER'] = $organizerURL;
|
||||
$parameters['ORGANIZER']['CN'] = $organizerCN;
|
||||
@ -1317,6 +1318,13 @@ class calendar_ical extends calendar_boupdate
|
||||
$event['owner'] = $event_info['stored_event']['owner'];
|
||||
}
|
||||
$event['caldav_name'] = $event_info['stored_event']['caldav_name'];
|
||||
|
||||
// as we no longer export event owner/ORGANIZER as only participant, we have to re-add owner as participant
|
||||
// to not loose him, as EGroupware knows events without owner/ORGANIZER as participant
|
||||
if (isset($event_info['stored_event']['participants'][$event['owner']]) && !isset($event['participant'][$event['owner']]))
|
||||
{
|
||||
$event['participant'][$event['owner']] = $event_info['stored_event']['participants'][$event['owner']];
|
||||
}
|
||||
}
|
||||
else // common adjustments for new events
|
||||
{
|
||||
|
@ -244,9 +244,10 @@ abstract class groupdav_handler
|
||||
* @param array &$options
|
||||
* @param int|string &$id on return self::$path_extension got removed
|
||||
* @param boolean &$return_no_access=false if set to true on call, instead of '403 Forbidden' the entry is returned and $return_no_access===false
|
||||
* @param boolean $ignore_if_match=false if true, ignore If-Match precondition
|
||||
* @return array|string entry on success, string with http-error-code on failure, null for PUT on an unknown id
|
||||
*/
|
||||
function _common_get_put_delete($method,&$options,&$id,&$return_no_access=false)
|
||||
function _common_get_put_delete($method,&$options,&$id,&$return_no_access=false,$ignore_if_match=false)
|
||||
{
|
||||
if (self::$path_extension) $id = basename($id,self::$path_extension);
|
||||
|
||||
@ -275,7 +276,7 @@ abstract class groupdav_handler
|
||||
$etag = $this->get_etag($entry);
|
||||
// If the clients sends an "If-Match" header ($_SERVER['HTTP_IF_MATCH']) we check with the current etag
|
||||
// of the calendar --> on failure we return 412 Precondition failed, to not overwrite the modifications
|
||||
if (isset($_SERVER['HTTP_IF_MATCH']))
|
||||
if (isset($_SERVER['HTTP_IF_MATCH']) && !$ignore_if_match)
|
||||
{
|
||||
$this->http_if_match = $_SERVER['HTTP_IF_MATCH'];
|
||||
// strip of quotes around etag, if they exist, that way we allow etag with and without quotes
|
||||
|
Loading…
Reference in New Issue
Block a user