* Calendar/CalDAV/eSync: fixed etag generation to NOT query maximum user-modification date for each entry, fixed etag and ctag for eSync to only use recurance master, as ActiveSync event does not contain extra participant data for exceptions (virtual and real)

This commit is contained in:
Ralf Becker 2012-03-12 08:20:36 +00:00
parent 6c4a1e0b72
commit 21c49b0b26
4 changed files with 64 additions and 38 deletions

View File

@ -1055,7 +1055,7 @@ return array(); // temporary disabling meeting requests from calendar
$attendee->email = $info['email']; $attendee->email = $info['email'];
if ($uid[0] == 'r') $attendee->type = 3; // 3 = resource if ($uid[0] == 'r') $attendee->type = 3; // 3 = resource
} }
// email must NOT be empy, but MAY be an arbitrary text // email must NOT be empty, but MAY be an arbitrary text
if (empty($attendee->email)) $attendee->email = 'noreply-'.$uid.'-uid@egroupware.org'; if (empty($attendee->email)) $attendee->email = 'noreply-'.$uid.'-uid@egroupware.org';
$message->attendees[] = $attendee; $message->attendees[] = $attendee;
@ -1196,7 +1196,7 @@ return array(); // temporary disabling meeting requests from calendar
{ {
if (!isset($this->calendar)) $this->calendar = new calendar_boupdate(); if (!isset($this->calendar)) $this->calendar = new calendar_boupdate();
if (!($etag = $this->calendar->get_etag($id))) if (!($etag = $this->calendar->get_etag($id, $nul, true, true))) // last true: $only_master=true
{ {
$stat = false; $stat = false;
// error_log why access is denied (should never happen for everything returned by calendar_bo::search) // error_log why access is denied (should never happen for everything returned by calendar_bo::search)
@ -1236,7 +1236,7 @@ return array(); // temporary disabling meeting requests from calendar
if ($type != 'calendar') return false; if ($type != 'calendar') return false;
if (!isset($this->calendar)) $this->calendar = new calendar_boupdate(); if (!isset($this->calendar)) $this->calendar = new calendar_boupdate();
$ctag = $this->calendar->get_ctag($owner); $ctag = $this->calendar->get_ctag($owner,'owner',true); // true only consider recurrence master
// workaround for syncstate = 0 when calendar is empty causes synctate to not return 0 but array resulting in foldersync loop // workaround for syncstate = 0 when calendar is empty causes synctate to not return 0 but array resulting in foldersync loop
if ($ctag == 0) $ctag = 1; if ($ctag == 0) $ctag = 1;
$changes = array(); // no change $changes = array(); // no change

View File

@ -407,6 +407,7 @@ class calendar_bo
* cols string|array columns to select, if set an iterator will be returned * cols string|array columns to select, if set an iterator will be returned
* append string to append to the query, eg. GROUP BY * append string to append to the query, eg. GROUP BY
* cfs array if set, query given custom fields or all for empty array, none are returned, if not set (default) * cfs array if set, query given custom fields or all for empty array, none are returned, if not set (default)
* master_only boolean default false, true only take into account participants/status from master (for AS)
* @param string $sql_filter=null sql to be and'ed into query (fully quoted), default none * @param string $sql_filter=null sql to be and'ed into query (fully quoted), default none
* @return iterator|array|boolean array of events or array with YYYYMMDD strings / array of events pairs (depending on $daywise param) * @return iterator|array|boolean array of events or array with YYYYMMDD strings / array of events pairs (depending on $daywise param)
* or false if there are no read-grants from _any_ of the requested users or iterator/recordset if cols are given * or false if there are no read-grants from _any_ of the requested users or iterator/recordset if cols are given
@ -621,6 +622,8 @@ class calendar_bo
*/ */
function clear_private_infos(&$event,$allowed_participants = array()) function clear_private_infos(&$event,$allowed_participants = array())
{ {
if (!is_array($event['participants'])) error_log(__METHOD__.'('.array2string($event).', '.array2string($allowed_participants).') NO PARTICIPANTS '.function_backtrace());
$event = array( $event = array(
'id' => $event['id'], 'id' => $event['id'],
'start' => $event['start'], 'start' => $event['start'],
@ -1917,9 +1920,11 @@ class calendar_bo
* @param array|int|string $event array with event or cal_id, or cal_id:recur_date for virtual exceptions * @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 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 * @param boolean $client_share_uid_excpetions Does client understand exceptions to be included in VCALENDAR component of series master sharing its UID
* @param boolean $master_only=false only take into account recurrance masters
* (for ActiveSync which does not support different participants/status on recurrences/exceptions!)
* @return string|boolean string with etag or false * @return string|boolean string with etag or false
*/ */
function get_etag($entry, &$schedule_tag=null, $client_share_uid_excpetions=true) function get_etag($entry, &$schedule_tag=null, $client_share_uid_excpetions=true,$master_only=false)
{ {
if (!is_array($entry)) if (!is_array($entry))
{ {
@ -1936,7 +1941,7 @@ class calendar_bo
} }
else else
{ {
$modified = max($this->so->max_user_modified($entry['id']), $entry['modified']); $modified = max($this->so->max_user_modified($entry['id'],false,$master_only), $entry['modified']);
} }
$etag .= ':' . $modified; $etag .= ':' . $modified;
// include exception etags into our own etag, if exceptions are included // include exception etags into our own etag, if exceptions are included
@ -1956,9 +1961,17 @@ class calendar_bo
{ {
if ($recurrence['reference'] && $recurrence['id'] != $entry['id']) // ignore series master if ($recurrence['reference'] && $recurrence['id'] != $entry['id']) // ignore series master
{ {
$etag .= ':'.$this->get_etag($recurrence, $full_etag); $exception_etag = $this->get_etag($recurrence, $nul);
// if $master_only, only add cal_etag, not max. user modification date
if ($master_only) list(,$exception_etag) = explode(':',$exception_etag);
$exception_etags .= ':'.$this->get_etag($recurrence, $nul);
} }
} }
if ($exception_etags)
{
$etag .= ':'.md5($exception_etags); // limit size, as there can be many exceptions
}
} }
//error_log(__METHOD__ . "($entry[id],$client_share_uid_excpetions) entry=".array2string($entry)." --> etag=$etag"); //error_log(__METHOD__ . "($entry[id],$client_share_uid_excpetions) entry=".array2string($entry)." --> etag=$etag");
return $etag; return $etag;
@ -1968,17 +1981,17 @@ class calendar_bo
* Query ctag for calendar * Query ctag for calendar
* *
* @param int|array $user integer user-id or array of user-id's to use, defaults to the current user * @param int|array $user integer user-id or array of user-id's to use, defaults to the current user
* @param $filter='owner' * @param string $filter='owner' all (not rejected), accepted, unknown, tentative, rejected or hideprivate
* @return string $filter='owner' all (not rejected), accepted, unknown, tentative, rejected or hideprivate * @param boolean $master_only=false only check recurance master (egw_cal_user.recur_date=0)
* @todo use MAX(modified) to query everything in one sql query, currently we do one query per event (more then the search) * @return integer
*/ */
public function get_ctag($user,$filter='owner') public function get_ctag($user, $filter='owner', $master_only=false)
{ {
if ($this->debug > 1) $startime = microtime(true); if ($this->debug > 1) $startime = microtime(true);
// resolve users to add memberships for users and members for groups // resolve users to add memberships for users and members for groups
$users = $this->resolve_users($user); $users = $this->resolve_users($user);
$ctag = $users ? $this->so->get_ctag($users, $filter == 'owner') : 0; // no rights, return 0 as ctag (otherwise we get SQL error!) $ctag = $users ? $this->so->get_ctag($users, $filter == 'owner', $master_only) : 0; // no rights, return 0 as ctag (otherwise we get SQL error!)
if ($this->debug > 1) error_log(__METHOD__. "($user, '$filter') = $ctag = ".date('Y-m-d H:i:s',$ctag)." took ".(microtime(true)-$startime)." secs"); if ($this->debug > 1) error_log(__METHOD__. "($user, '$filter') = $ctag = ".date('Y-m-d H:i:s',$ctag)." took ".(microtime(true)-$startime)." secs");
return $ctag; return $ctag;

View File

@ -225,23 +225,8 @@ class calendar_groupdav extends groupdav_handler
$events =& $this->bo->search($filter); $events =& $this->bo->search($filter);
if ($events) if ($events)
{ {
// get all max user modified times at once
foreach($events as $k => $event)
{
if ($this->client_shared_uid_exceptions && $event['reference'])
{
throw new egw_exception_assertion_failed(__METHOD__."() event=".array2string($event));
// this exception will be handled with the series master
unset($events[$k]);
continue;
}
$ids[] = $event['id'];
}
$max_user_modified = $this->bo->so->max_user_modified($ids);
foreach($events as $event) foreach($events as $event)
{ {
$event['max_user_modified'] = $max_user_modified[$event['id']];
$etag = $this->get_etag($event, $schedule_tag); $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'])); //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( $props = array(

View File

@ -293,18 +293,21 @@ class calendar_so
* This includes ALL recurences of an event series * This includes ALL recurences of an event series
* *
* @param int|array $ids one or multiple cal_id's * @param int|array $ids one or multiple cal_id's
* @param booelan $return_maximum=false if true return only the maximum, even for multiple ids * @param boolean $return_maximum=false if true return only the maximum, even for multiple ids
* @param boolean $master_only=false only check recurance master (egw_cal_user.recur_date=0)
* @return int|array (array of) modification timestamp(s) * @return int|array (array of) modification timestamp(s)
*/ */
function max_user_modified($ids, $return_maximum=false) function max_user_modified($ids, $return_maximum=false, $master_only=false)
{ {
if (!is_array($ids)) $return_maximum = true; if (!is_array($ids)) $return_maximum = true;
$where = array('cal_id' => $ids);
if ($master_only) $where['recur_date'] = 0;
if ($return_maximum) if ($return_maximum)
{ {
if (($etags = $this->db->select($this->user_table,'MAX(cal_user_modified)',array( if (($etags = $this->db->select($this->user_table,'MAX(cal_user_modified)',$where,
'cal_id' => $ids, __LINE__,__FILE__,false,'','calendar')->fetchColumn()))
),__LINE__,__FILE__,false,'','calendar')->fetchColumn()))
{ {
$etags = $this->db->from_timestamp($etags); $etags = $this->db->from_timestamp($etags);
} }
@ -312,15 +315,13 @@ class calendar_so
else else
{ {
$etags = array(); $etags = array();
foreach($this->db->select($this->user_table,'cal_id,MAX(cal_user_modified) AS user_etag',array( foreach($this->db->select($this->user_table,'cal_id,MAX(cal_user_modified) AS user_etag',$where,
'cal_id' => $ids, __LINE__,__FILE__,false,'GROUP BY cal_id','calendar') as $row)
),__LINE__,__FILE__,false,'GROUP BY cal_id','calendar') as $row)
{ {
$etags[$row['cal_id']] = $this->db->from_timestamp($row['user_etag']); $etags[$row['cal_id']] = $this->db->from_timestamp($row['user_etag']);
} }
} }
//echo "<p>".__METHOD__.'('.array2string($ids).','.array($return_maximum).') = '.array2string($etags)."</p>\n"; //error_log(__METHOD__.'('.array2string($ids).', '.array2string($return_maximum).', '.array2string($master_only).') = '.array2string($etags).' '.function_backtrace());
//error_log(__METHOD__.'('.array2string($ids).','.array2string($return_maximum).') = '.array2string($etags));
return $etags; return $etags;
} }
@ -331,14 +332,19 @@ class calendar_so
* *
* @param int|array $users one or mulitple calendar users * @param int|array $users one or mulitple calendar users
* @param booelan $owner_too=false if true return also events owned by given users * @param booelan $owner_too=false if true return also events owned by given users
* @param boolean $master_only=false only check recurance master (egw_cal_user.recur_date=0)
* @return int maximum modification timestamp * @return int maximum modification timestamp
*/ */
function get_ctag($users, $owner_too=false) function get_ctag($users, $owner_too=false,$master_only=false)
{ {
$where = array( $where = array(
'cal_user_type' => 'u', 'cal_user_type' => 'u',
'cal_user_id' => $users, 'cal_user_id' => $users,
); );
if ($master_only)
{
$where['cal_recur_date'] = 0;
}
if ($owner_too) if ($owner_too)
{ {
// owner can only by users, no groups // owner can only by users, no groups
@ -406,6 +412,7 @@ class calendar_so
* @param string $params['append'] SQL to append to the query before $order, eg. for a GROUP BY clause * @param string $params['append'] SQL to append to the query before $order, eg. for a GROUP BY clause
* @param array $params['cfs'] custom fields to query, null = none, array() = all, or array with cfs names * @param array $params['cfs'] custom fields to query, null = none, array() = all, or array with cfs names
* @param array $params['users'] raw parameter as passed to calendar_bo::search() no memberships resolved! * @param array $params['users'] raw parameter as passed to calendar_bo::search() no memberships resolved!
* @param boolean $params['master_only']=false, true only take into account participants/status from master (for AS)
* @param int $remove_rejected_by_user=null add join to remove entry, if given user has rejected it * @param int $remove_rejected_by_user=null add join to remove entry, if given user has rejected it
* @return array of cal_ids, or false if error in the parameters * @return array of cal_ids, or false if error in the parameters
* *
@ -702,10 +709,13 @@ class calendar_so
//_debug_array($events); //_debug_array($events);
if (count($ids)) if (count($ids))
{ {
$ids = array_unique($ids);
$need_max_user_modified = array();
// now ready all users with the given cal_id AND (cal_recur_date=0 or the fitting recur-date) // now ready all users with the given cal_id AND (cal_recur_date=0 or the fitting recur-date)
// This will always read the first entry of each recuring event too, we eliminate it later // This will always read the first entry of each recuring event too, we eliminate it later
$recur_dates[] = 0; $recur_dates[] = 0;
$utcal_id_view = " (SELECT * FROM ".$this->user_table." WHERE cal_id IN (".implode(',',array_unique($ids)).") AND cal_status!='X') utcalid "; $utcal_id_view = " (SELECT * FROM ".$this->user_table." WHERE cal_id IN (".implode(',',$ids).") AND cal_status!='X') utcalid ";
//$utrecurdate_view = " (select * from ".$this->user_table." where cal_recur_date in (".implode(',',array_unique($recur_dates)).")) utrecurdates "; //$utrecurdate_view = " (select * from ".$this->user_table." where cal_recur_date in (".implode(',',array_unique($recur_dates)).")) utrecurdates ";
foreach($this->db->select($utcal_id_view,'*',array( foreach($this->db->select($utcal_id_view,'*',array(
//'cal_id' => array_unique($ids), //'cal_id' => array_unique($ids),
@ -734,6 +744,24 @@ class calendar_so
// set data, if recurrence is requested // set data, if recurrence is requested
if (isset($events[$id])) $events[$id]['participants'][$uid] = $status; if (isset($events[$id])) $events[$id]['participants'][$uid] = $status;
// fill max_user_modified:
if (!$params['master_only'] && $events[$id]['recur_type'])
{
$need_max_user_modified[$id] = $id;
}
elseif (isset($events[$id]) && ($modified = $this->db->from_timestamp($row['cal_user_modified'])) > $events[$id]['max_user_modified'])
{
$events[$id]['max_user_modified'] = $modified;
}
}
// max_user_modified for recurring events has to include all recurrences, above code only querys $recur_date!
if (!$params['enum_recuring'] && $need_max_user_modified)
{
foreach($this->max_user_modified($need_max_user_modified) as $id => $modified)
{
$events[$id]['max_user_modified'] = $modified;
}
} }
//custom fields are not shown in the regular views, so we only query them, if explicitly required //custom fields are not shown in the regular views, so we only query them, if explicitly required
if (!is_null($params['cfs'])) if (!is_null($params['cfs']))