From 21c49b0b26699bbb40814b0a4cd0cbd6c787bab5 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Mon, 12 Mar 2012 08:20:36 +0000 Subject: [PATCH] * 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) --- .../inc/class.calendar_activesync.inc.php | 6 +-- calendar/inc/class.calendar_bo.inc.php | 29 ++++++++--- calendar/inc/class.calendar_groupdav.inc.php | 15 ------ calendar/inc/class.calendar_so.inc.php | 52 ++++++++++++++----- 4 files changed, 64 insertions(+), 38 deletions(-) diff --git a/calendar/inc/class.calendar_activesync.inc.php b/calendar/inc/class.calendar_activesync.inc.php index 1f7f3a5023..4a790352c9 100644 --- a/calendar/inc/class.calendar_activesync.inc.php +++ b/calendar/inc/class.calendar_activesync.inc.php @@ -1055,7 +1055,7 @@ return array(); // temporary disabling meeting requests from calendar $attendee->email = $info['email']; 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'; $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 (!($etag = $this->calendar->get_etag($id))) + if (!($etag = $this->calendar->get_etag($id, $nul, true, true))) // last true: $only_master=true { $stat = false; // 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 (!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 if ($ctag == 0) $ctag = 1; $changes = array(); // no change diff --git a/calendar/inc/class.calendar_bo.inc.php b/calendar/inc/class.calendar_bo.inc.php index 438af26217..44ec0f9883 100644 --- a/calendar/inc/class.calendar_bo.inc.php +++ b/calendar/inc/class.calendar_bo.inc.php @@ -407,6 +407,7 @@ class calendar_bo * cols string|array columns to select, if set an iterator will be returned * 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) + * 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 * @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 @@ -621,6 +622,8 @@ class calendar_bo */ 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( 'id' => $event['id'], '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 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 $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 */ - 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)) { @@ -1936,7 +1941,7 @@ class calendar_bo } 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; // 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 { - $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"); return $etag; @@ -1968,17 +1981,17 @@ class calendar_bo * 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 $filter='owner' - * @return string $filter='owner' all (not rejected), accepted, unknown, tentative, rejected or hideprivate - * @todo use MAX(modified) to query everything in one sql query, currently we do one query per event (more then the search) + * @param 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) + * @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); // resolve users to add memberships for users and members for groups $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"); return $ctag; diff --git a/calendar/inc/class.calendar_groupdav.inc.php b/calendar/inc/class.calendar_groupdav.inc.php index 2ae40c8791..babf7fcfca 100644 --- a/calendar/inc/class.calendar_groupdav.inc.php +++ b/calendar/inc/class.calendar_groupdav.inc.php @@ -225,23 +225,8 @@ class calendar_groupdav extends groupdav_handler $events =& $this->bo->search($filter); 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) { - $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( diff --git a/calendar/inc/class.calendar_so.inc.php b/calendar/inc/class.calendar_so.inc.php index 2b6731e7f6..5ca0d24660 100644 --- a/calendar/inc/class.calendar_so.inc.php +++ b/calendar/inc/class.calendar_so.inc.php @@ -293,18 +293,21 @@ class calendar_so * This includes ALL recurences of an event series * * @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) */ - 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; + $where = array('cal_id' => $ids); + if ($master_only) $where['recur_date'] = 0; + if ($return_maximum) { - if (($etags = $this->db->select($this->user_table,'MAX(cal_user_modified)',array( - 'cal_id' => $ids, - ),__LINE__,__FILE__,false,'','calendar')->fetchColumn())) + if (($etags = $this->db->select($this->user_table,'MAX(cal_user_modified)',$where, + __LINE__,__FILE__,false,'','calendar')->fetchColumn())) { $etags = $this->db->from_timestamp($etags); } @@ -312,15 +315,13 @@ class calendar_so else { $etags = array(); - foreach($this->db->select($this->user_table,'cal_id,MAX(cal_user_modified) AS user_etag',array( - 'cal_id' => $ids, - ),__LINE__,__FILE__,false,'GROUP BY cal_id','calendar') as $row) + foreach($this->db->select($this->user_table,'cal_id,MAX(cal_user_modified) AS user_etag',$where, + __LINE__,__FILE__,false,'GROUP BY cal_id','calendar') as $row) { $etags[$row['cal_id']] = $this->db->from_timestamp($row['user_etag']); } } - //echo "

".__METHOD__.'('.array2string($ids).','.array($return_maximum).') = '.array2string($etags)."

\n"; - //error_log(__METHOD__.'('.array2string($ids).','.array2string($return_maximum).') = '.array2string($etags)); + //error_log(__METHOD__.'('.array2string($ids).', '.array2string($return_maximum).', '.array2string($master_only).') = '.array2string($etags).' '.function_backtrace()); return $etags; } @@ -331,14 +332,19 @@ class calendar_so * * @param int|array $users one or mulitple calendar 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 */ - function get_ctag($users, $owner_too=false) + function get_ctag($users, $owner_too=false,$master_only=false) { $where = array( 'cal_user_type' => 'u', 'cal_user_id' => $users, ); + if ($master_only) + { + $where['cal_recur_date'] = 0; + } if ($owner_too) { // 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 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 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 * @return array of cal_ids, or false if error in the parameters * @@ -702,10 +709,13 @@ class calendar_so //_debug_array($events); 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) // This will always read the first entry of each recuring event too, we eliminate it later $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 "; foreach($this->db->select($utcal_id_view,'*',array( //'cal_id' => array_unique($ids), @@ -734,6 +744,24 @@ class calendar_so // set data, if recurrence is requested 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 if (!is_null($params['cfs']))