diff --git a/api/src/CalDAV.php b/api/src/CalDAV.php
index 34f6b88ba6..88b395a41e 100644
--- a/api/src/CalDAV.php
+++ b/api/src/CalDAV.php
@@ -1311,7 +1311,7 @@ class CalDAV extends HTTP_WebDAV_Server
{
$collection_props = $this->props2array($file['props']);
echo '
'.lang('Collection listing').': '.htmlspecialchars($collection_props['DAV:displayname'])."
\n";
- continue; // own entry --> displaying properies later
+ continue; // own entry --> displaying properties later
}
if(!$n++)
{
@@ -1322,7 +1322,7 @@ class CalDAV extends HTTP_WebDAV_Server
}
echo "\t\n";
}
- $props = $this->props2array($file['props']);
+ $props = $this->props2array($file['props'] ?? []);
//echo $file['path']; _debug_array($props);
$class = $class === 'row_on' ? 'row_off' : 'row_on';
diff --git a/calendar/inc/class.calendar_groupdav.inc.php b/calendar/inc/class.calendar_groupdav.inc.php
index cd2c92ec78..36515f3907 100644
--- a/calendar/inc/class.calendar_groupdav.inc.php
+++ b/calendar/inc/class.calendar_groupdav.inc.php
@@ -259,7 +259,7 @@ class calendar_groupdav extends Api\CalDAV\Handler
if (isset($nresults))
{
unset($filter['no_total']); // we need the total!
- $files['files'] = $this->propfind_generator($path, $filter, [], (int)$nresults);
+ $files['files'] = $this->propfind_generator($path, $filter, $files['files'], (int)$nresults);
// hack to support limit with sync-collection report: events are returned in modified ASC order (oldest first)
// if limit is smaller than full result, return modified-1 as sync-token, so client requests next chunk incl. modified
@@ -368,21 +368,35 @@ class calendar_groupdav extends Api\CalDAV\Handler
{
foreach($events as $event)
{
- // remove event from requested multiget ids, to be able to report not found urls
- if (!empty($this->requested_multiget_ids) && ($k = array_search($event[self::$path_attr], $this->requested_multiget_ids)) !== false)
- {
- unset($this->requested_multiget_ids[$k]);
- }
+ $no_active_participants = !$this->hasActiveParticipants($event, $filter['users'], $events);
+
// sync-collection report: deleted entries need to be reported without properties, same for rejected or deleted invitations
- if ($sync_collection && ($event['deleted'] && !$event['cal_reference'] || in_array($event['participants'][$filter['users']][0], array('R','X'))))
+ if ($sync_collection && ($event['deleted'] && !$event['cal_reference'] ||
+ //in_array($event['participants'][$filter['users']][0] ?? '', array('R','X')) ||
+ $no_active_participants))
{
if (++$yielded && isset($nresults) && $yielded > $nresults)
{
return;
}
+ // remove event from requested multiget ids, to be able to report not found urls
+ if (!empty($this->requested_multiget_ids) && ($k = array_search($event[self::$path_attr], $this->requested_multiget_ids)) !== false)
+ {
+ unset($this->requested_multiget_ids[$k]);
+ }
yield ['path' => $path.urldecode($this->get_path($event))];
continue;
}
+ // for a regular propfind/multiget-report we must NOT return deleted or rejected events
+ if (!$sync_collection && $no_active_participants)
+ {
+ continue;
+ }
+ // remove event from requested multiget ids, to be able to report not found urls
+ if (!empty($this->requested_multiget_ids) && ($k = array_search($event[self::$path_attr], $this->requested_multiget_ids)) !== false)
+ {
+ unset($this->requested_multiget_ids[$k]);
+ }
$schedule_tag = null;
$etag = $this->get_etag($event, $schedule_tag);
@@ -407,7 +421,7 @@ class calendar_groupdav extends Api\CalDAV\Handler
$content = $this->iCal($event, $filter['users'],
strpos($path, '/inbox/') !== false ? 'REQUEST' : null,
!isset($calendar_data['children']['expand']) ? false :
- ($calendar_data['children']['expand']['attrs'] ? $calendar_data['children']['expand']['attrs'] : true));
+ ($calendar_data['children']['expand']['attrs'] ? $calendar_data['children']['expand']['attrs'] : true), $events);
$props['getcontentlength'] = bytes($content);
$props['calendar-data'] = Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-data',$content);
}
@@ -454,6 +468,59 @@ class calendar_groupdav extends Api\CalDAV\Handler
}
}
+ /**
+ * Check an event has active (not rejected or deleted) participants against an (array of) user- or group-id(s)
+ *
+ * For recurring events / series-master we have to check all exceptions too!
+ * For a group-id we also check against all members!
+ *
+ * @param array $event
+ * @param int|int[] $users user(s) to check
+ * @param array|null &$events on return exceptions to the series as returned by self::get_series() ($expand=false!)
+ * @return bool true: user(s) is an active participant, or false if not (incl. all exceptions for recurring events)
+ */
+ protected function hasActiveParticipants(array $event, $users, array &$events=null) : bool
+ {
+ $events = null;
+ if (!is_array($users))
+ {
+ $users = [$users];
+ if ($users[0] < 0)
+ {
+ $users = array_merge($users, Api\Accounts::getInstance()->members($users[0], true) ?: []);
+ }
+ }
+ // return true event(-master) has active participants
+ foreach(array_intersect_key($event['participants'], array_flip($users)) as $status)
+ {
+ if (!in_array($status[0], ['R', 'X']))
+ {
+ //error_log(__METHOD__."(cal_id=$event[id]: $event[title], cal_reference=$event[cal_reference], participants=".json_encode($event['participants']).", ".json_encode($users)." returning true");
+ return true;
+ }
+ }
+ // return false for regular / non-recurring events
+ if (empty($event['recur_type']))
+ {
+ return false;
+ }
+
+ // check exceptions for recurring event
+ $events =& self::get_series($event['uid'], $this->bo, false, $users[0], $event);
+ foreach($events as $exception)
+ {
+ if (empty($exception['reference'])) continue; // master / identical to $event and already checked
+
+ if ($this->hasActiveParticipants($exception, $users))
+ {
+ return true;
+ }
+ }
+ //error_log(__METHOD__."(cal_id=$event[id]: $event[title], cal_reference=$event[cal_reference], participants=".json_encode($event['participants']).", ".json_encode($users)." returning false");
+ // if we get here, given user(s) are NOT an active participant of any exception or has been deleted or rejected them all
+ return false;
+ }
+
/**
* Return Calendarserver:(created|updated)-by sub-properties for a given user and time
*
@@ -502,7 +569,7 @@ class calendar_groupdav extends Api\CalDAV\Handler
* @param array $options
* @param array &$cal_filters
* @param string $id
- * @param int &$nresult on return limit for number or results or unchanged/null
+ * @param int &$nresults on return limit for number of results or unchanged/null
* @return boolean true if filter could be processed
*/
function _report_filters($options, &$cal_filters, $id, &$nresults)
@@ -648,7 +715,11 @@ class calendar_groupdav extends Api\CalDAV\Handler
$cal_filters['query'][self::$path_attr] = $this->requested_multiget_ids;
}
- if ($this->debug > 1) error_log(__FILE__ . __METHOD__ ."($options[path],...,$id) calendar-multiget: ids=".implode(',',$ids).', cal_filters='.array2string($cal_filters));
+ if ($this->debug > 1)
+ {
+ error_log(__FILE__ . __METHOD__ ."($options[path],...,$id) calendar-multiget: ids=".
+ implode(',', $this->requested_multiget_ids).', cal_filters='.json_encode($cal_filters));
+ }
}
return true;
}
@@ -689,9 +760,10 @@ class calendar_groupdav extends Api\CalDAV\Handler
* @param int $user =null account_id of calendar to display
* @param string $method =null eg. 'PUBLISH' for inbox, nothing anywhere else
* @param boolean|array $expand =false true or array with values for 'start', 'end' to expand recurrences
+ * @param array|null $events as returned by get_series() for !$expand (to not read them again)
* @return string
*/
- private function iCal(array $event,$user=null, $method=null, $expand=false)
+ private function iCal(array $event,$user=null, $method=null, $expand=false, array $events=null)
{
static $handler = null;
if (is_null($handler)) $handler = $this->_get_handler();
@@ -730,9 +802,7 @@ class calendar_groupdav extends Api\CalDAV\Handler
$event['description'] = lang('Videoconference').":\n$link\n\n".$event['description'];
}
- $events = array($event);
-
- // for recuring events we have to add the exceptions
+ // for recurring events we have to add the exceptions
if ($this->client_shared_uid_exceptions && $event['recur_type'] && !empty($event['uid']))
{
if (is_array($expand))
@@ -741,14 +811,21 @@ class calendar_groupdav extends Api\CalDAV\Handler
if (isset($expand['end'])) $expand['end'] = $this->vCalendar->_parseDateTime($expand['end']);
}
// pass in original event as master, as it has correct start-date even if first recurrence is an exception
- $events =& self::get_series($event['uid'], $this->bo, $expand, $user, $event);
-
+ if ($expand || !isset($events))
+ {
+ $events =& self::get_series($event['uid'], $this->bo, $expand, $user, $event);
+ }
// as alarm is now only on next recurrence, set alarm from original event on master
if ($event['alarm']) $events[0]['alarm'] = $event['alarm'];
}
- elseif(!$this->client_shared_uid_exceptions && $event['reference'])
+ else
{
- $events[0]['uid'] .= '-'.$event['id']; // force a different uid
+ $events = array($event);
+
+ if(!$this->client_shared_uid_exceptions && $event['reference'])
+ {
+ $events[0]['uid'] .= '-'.$event['id']; // force a different uid
+ }
}
return $handler->exportVCal($events, '2.0', $method);
}
@@ -780,7 +857,8 @@ class calendar_groupdav extends Api\CalDAV\Handler
if (!($events =& $bo->search($params)))
{
- return array();
+ $events = [];
+ return $events;
}
// find master, which is not always first event, eg. when first event is an exception