From: <jl...@us...> - 2009-06-30 19:23:18
|
Author: jlehrke Date: Tue Jun 30 21:22:41 2009 New Revision: 27337 URL: http://www.egroupware.org/viewvc/egroupware?rev=27337&view=rev Log: Fix recurrence problems Modified: branches/SyncML-1.2/calendar/inc/class.calendar_bo.inc.php branches/SyncML-1.2/calendar/inc/class.calendar_boupdate.inc.php branches/SyncML-1.2/calendar/inc/class.calendar_ical.inc.php branches/SyncML-1.2/calendar/inc/class.calendar_so.inc.php branches/SyncML-1.2/calendar/inc/class.calendar_uiforms.inc.php Modified: branches/SyncML-1.2/calendar/inc/class.calendar_bo.inc.php URL: http://www.egroupware.org/viewvc/egroupware/branches/SyncML-1.2/calendar/inc/class.calendar_bo.inc.php?rev=27337&r1=27336&r2=27337&view=diff ============================================================================== --- branches/SyncML-1.2/calendar/inc/class.calendar_bo.inc.php (original) +++ branches/SyncML-1.2/calendar/inc/class.calendar_bo.inc.php Tue Jun 30 21:22:41 2009 @@ -571,9 +571,17 @@ $events = array(); $this->insert_all_repetitions($event,$start,$this->date2ts($this->config['horizont'],true),$events,null); - + $days = $this->so->get_recurrence_exceptions($event); + $days = is_array($days) ? $days : array(); + //error_log('set_recurrences: days' . print_r($days, true) ); foreach($events as $event) { + //error_log('set_recurrences: start = ' . $event['start'] ); + if (in_array($event['start'], $days)) + { + // we don't change the stati of recurrence exceptions + $event['participants'] = array(); + } $this->so->recurrence($event['id'],$this->date2ts($event['start'],true),$this->date2ts($event['end'],true),$event['participants']); } } @@ -754,6 +762,8 @@ for($ts = $start_ts; $ts < $end_ts; $ts += DAY_s) { $search_date_ymd = (int)$this->date2string($ts); + + //error_log('insert_all_repetitions search_date = ' . $search_date_ymd . ' => ' . print_r($recur_exceptions, true)); $have_exception = !is_null($recur_exceptions) && isset($recur_exceptions[$search_date_ymd]); Modified: branches/SyncML-1.2/calendar/inc/class.calendar_boupdate.inc.php URL: http://www.egroupware.org/viewvc/egroupware/branches/SyncML-1.2/calendar/inc/class.calendar_boupdate.inc.php?rev=27337&r1=27336&r2=27337&view=diff ============================================================================== --- branches/SyncML-1.2/calendar/inc/class.calendar_boupdate.inc.php (original) +++ branches/SyncML-1.2/calendar/inc/class.calendar_boupdate.inc.php Tue Jun 30 21:22:41 2009 @@ -711,7 +711,7 @@ if (($cal_id = $this->so->save($event,$set_recurrences,NULL,$event['etag'])) && $set_recurrences && $event['recur_type'] != MCAL_RECUR_NONE) { $save_event['id'] = $cal_id; - $this->set_recurrences($save_event); + $this->set_recurrences($save_event, 0); } $GLOBALS['egw']->contenthistory->updateTimeStamp('calendar',$cal_id,$event['id'] ? 'modify' : 'add',time()); @@ -752,13 +752,14 @@ * @param string/int $uid account_id or 1-char type-identifer plus id (eg. c15 for addressbook entry #15) * @param int/char $status numeric status (defines) or 1-char code: 'R', 'U', 'T' or 'A' * @param int $recur_date=0 date to change, or 0 = all since now + * @param boolean $ignore_acl=false do not check the permisions for the $uid, if true * @return int number of changed recurrences */ - function set_status($event,$uid,$status,$recur_date=0) + function set_status($event,$uid,$status,$recur_date=0, $ignore_acl=false) { $cal_id = is_array($event) ? $event['id'] : $event; //echo "<p>bocalupdate::set_status($cal_id,$uid,$status,$recur_date)</p>\n"; - if (!$cal_id || !$this->check_status_perms($uid,$event)) + if (!$cal_id || (!$ignore_acl && !$this->check_status_perms($uid,$event))) { return false; } Modified: branches/SyncML-1.2/calendar/inc/class.calendar_ical.inc.php URL: http://www.egroupware.org/viewvc/egroupware/branches/SyncML-1.2/calendar/inc/class.calendar_ical.inc.php?rev=27337&r1=27336&r2=27337&view=diff ============================================================================== --- branches/SyncML-1.2/calendar/inc/class.calendar_ical.inc.php (original) +++ branches/SyncML-1.2/calendar/inc/class.calendar_ical.inc.php Tue Jun 30 21:22:41 2009 @@ -314,12 +314,12 @@ case 'DTEND': // write start + end of whole day events as dates - if (date('H:i:s',$event['start']) == '00:00:00' && in_array(date('H:i:s',$event['end']),array('23:59:59','23:59:00'))) - { - $event['end'] += 12*3600; // we need the date of the next day, as DTEND is non-inclusive (= exclusive) in rfc2445 - foreach(array('start' => 'DTSTART','end' => 'DTEND') as $f => $t) - { - $arr = calendar_bo::date2array($event[$f]); + if ($this->isWholeDay($event)) + { + $event['end-nextday'] = $event['end'] + 12*3600; // we need the date of the next day, as DTEND is non-inclusive (= exclusive) in rfc2445 + foreach(array('start' => 'DTSTART','end-nextday' => 'DTEND') as $f => $t) + { + $arr = $this->date2array($event[$f]); $vevent->setAttribute($t, array('year' => $arr['year'],'month' => $arr['month'],'mday' => $arr['day']), array('VALUE' => 'DATE')); } @@ -362,12 +362,6 @@ { $recur_enddate = (int)$event['recur_enddate']; $recur_enddate += 24 * 60 * 60 - 1; - //if ($palm_enddate_workaround) - //{ - // $recur_enddate += 86400; - //} - # append T and the Endtime, since the RRULE seems not to be understood by the client without it - //$rrule['UNTIL'] = date('Ymd',$recur_enddate).'T'.date('His',($event['end']?$event['end']:$event['start'])) ; $rrule['UNTIL'] = $vcal->_exportDateTime($recur_enddate); } else @@ -407,9 +401,14 @@ // We use end of day in vCal $recur_enddate = (int)$event['recur_enddate']; $recur_enddate += 24 * 60 * 60 - 1; - # append T and the Endtime, since the RRULE seems not to be understood by the client without it - //$rrule['UNTIL'] = date('Ymd',$event['recur_enddate']).'T'.date('His',($event['end']?$event['end']:$event['start'])); - $rrule['UNTIL'] = $vcal->_exportDateTime($recur_enddate); + if ($this->isWholeDay($event)) + { + $rrule['UNTIL'] = date('Ymd', $recur_enddate); + } + else + { + $rrule['UNTIL'] = $vcal->_exportDateTime($recur_enddate); + } } // no idea how to get the Horde parser to produce a standard conformant // RRULE:FREQ=... (note the double colon after RRULE, we cant use the $parameter array) @@ -457,9 +456,27 @@ { $days = array_unique($days); sort($days); + // use 'DATE' instead of 'DATE-TIME' on whole day events + if ($this->isWholeDay($event)) + { + $value_type = 'DATE'; + foreach($days as $id => $timestamp) + { + $arr = $this->date2array($timestamp); + $days[$id] = array( + 'year' => $arr['year'], + 'month' => $arr['month'], + 'mday' => $arr['day'], + ); + } + } + else + { + $value_type = 'DATE-TIME'; + } $attributes['EXDATE'] = ''; $values['EXDATE'] = $days; - $parameters['EXDATE']['VALUE'] = 'DATE-TIME'; + $parameters['EXDATE']['VALUE'] = $value_type; } break; @@ -487,11 +504,37 @@ if ($recur_date) { // We handle a status only exception - $attributes['RECURRENCE-ID'] = $recur_date; + if ($this->isWholeDay($event)) + { + $arr = $this->date2array($recur_date); + $vevent->setAttribute('RECURRENCE-ID', array( + 'year' => $arr['year'], + 'month' => $arr['month'], + 'mday' => $arr['day']), + array('VALUE' => 'DATE') + ); + } + else + { + $attributes['RECURRENCE-ID'] = $recur_date; + } } elseif ($event['reference']) { - $attributes['RECURRENCE-ID'] = $event['reference']; + if ($this->isWholeDay($event)) + { + $arr = $this->date2array($event['reference']); + $vevent->setAttribute('RECURRENCE-ID', array( + 'year' => $arr['year'], + 'month' => $arr['month'], + 'mday' => $arr['day']), + array('VALUE' => 'DATE') + ); + } + else + { + $attributes['RECURRENCE-ID'] = $event['reference']; + } } break; @@ -816,7 +859,7 @@ { // check if current user is an attendee and tried to change his status $this->set_status($egw_event, $this->user, - ($event['participants'][$this->user] ? $event['participants'][$this->user] : 'R'), $recur_date); + ($event['participants'][$this->user] ? $event['participants'][$this->user] : 'R'), $recur_date, true); } $Ok = $egw_event['id'] . ':' . $recur_date; continue; // nothing more to do @@ -824,7 +867,7 @@ else { // We need to create an new exception - $egw_event['recur_exception'] = array_merge($egw_event['recur_exception'], array($recur_date)); + $egw_event['recur_exception'] = array_unique(array_merge($egw_event['recur_exception'], array($recur_date))); $this->update($egw_event, true); $event['category'] = $egw_event['category']; $cal_id = -1; @@ -846,7 +889,27 @@ } else { - // Not recurring events + // We handle a single event or the series master + $days = $this->so->get_recurrence_exceptions($event); + if (is_array($days)) + { + Horde::logMessage("importVCAL event\n" . print_r($event, true), + __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage("importVCAL days\n" . print_r($days, true), + __FILE__, __LINE__, PEAR_LOG_DEBUG); + // remove all known "stati only" exceptions + $recur_exceptions = array(); + foreach ($event['recur_exception'] as $recur_exception) + { + if (!in_array($recur_exception, $days)) + { + $recur_exceptions[] = $recur_exception; + } + } + Horde::logMessage("importVCAL exceptions\n" . print_r($recur_exceptions, true), + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $event['recur_exception'] = $recur_exceptions; + } } if ($cal_id <= 0) @@ -1308,18 +1371,23 @@ $minimum_uid_length = 8; } + $isDate = false; $event = array(); $alarms = array(); $vcardData = array( 'recur_type' => MCAL_RECUR_NONE, 'recur_exception' => array(), ); - // we parse DTSTART first foreach ($component->_attributes as $attributes) { if ($attributes['name'] == 'DTSTART') { + if (isset($attributes['params']['VALUE']) + && $attributes['params']['VALUE'] == 'DATE') + { + $isDate = true; + } $vcardData['start'] = $attributes['value']; } } @@ -1554,8 +1622,9 @@ } break; case 'EXDATE': - if (isset($attributes['params']['VALUE']) - && $attributes['params']['VALUE'] == 'DATE') + if ((isset($attributes['params']['VALUE']) + && $attributes['params']['VALUE'] == 'DATE') || + (!isset($attributes['params']['VALUE']) && $isDate)) { $days = array(); $hour = date('H', $vcardData['start']); @@ -1873,9 +1942,17 @@ return $vcal->exportvCalendar(); } + /** + * update the status of all participant for a given recurrence or for all recurrences since now (includes recur_date=0) + * + * @param array $new_event event-array with the new stati + * @param array $old_event event-array with the old stati + * @param int $recur_date=0 date to change, or 0 = all since now + */ function update_status($new_event, $old_event , $recur_date) { - //error_log(__FILE__ . __METHOD__ . "\nnew_event:" . print_r($new_event,true)); + //error_log(__FILE__ . __METHOD__ . "\nold_event:" . print_r($old_event, true) + // . "\nnew_event:" . print_r($new_event, true)); // check the old list against the new list foreach ($old_event['participants'] as $userid => $status) @@ -1885,13 +1962,14 @@ $new_event['participants'][$userid] = 'G'; } elseif ($new_event['participants'][$userid] == $status){ // Same status -- nothing to do. - unset ($new_event['participants'][$userid]); + unset($new_event['participants'][$userid]); } } // write the changes foreach ($new_event['participants'] as $userid => $status) { - $this->set_status($old_event, $userid, $status, $recur_date); + //error_log(__FILE__ . __METHOD__ . "\n$userid => $status:"); + $this->set_status($old_event, $userid, $status, $recur_date, true); } } Modified: branches/SyncML-1.2/calendar/inc/class.calendar_so.inc.php URL: http://www.egroupware.org/viewvc/egroupware/branches/SyncML-1.2/calendar/inc/class.calendar_so.inc.php?rev=27337&r1=27336&r2=27337&view=diff ============================================================================== --- branches/SyncML-1.2/calendar/inc/class.calendar_so.inc.php (original) +++ branches/SyncML-1.2/calendar/inc/class.calendar_so.inc.php Tue Jun 30 21:22:41 2009 @@ -512,7 +512,7 @@ } //echo "<p>socal::save(,$change_since) event="; _debug_array($event); - //error_log(__METHOD__."(".str_replace(array("\n",' '),'',print_r($event,true)).",$set_recurrences,$change_since,$etag)"); + error_log(__METHOD__."(".str_replace(array("\n",' '),'',print_r($event,true)).",$set_recurrences,$change_since,$etag)"); $cal_id = (int) $event['id']; unset($event['id']); @@ -554,6 +554,7 @@ } else { + // new event if (!$event['cal_owner']) $event['cal_owner'] = $GLOBALS['egw_info']['user']['account_id']; if (!$event['cal_id'] && !isset($event['cal_uid'])) $event['cal_uid'] = ''; // uid is NOT NULL! @@ -586,26 +587,27 @@ { if (isset($event['recur_exception']) && is_array($event['recur_exception']) && count($event['recur_exception'])) { - // delete execeptions from the user and dates table, it could be the first time - $this->db->delete($this->user_table,array('cal_id' => $cal_id,'cal_recur_date' => $event['recur_exception']),__LINE__,__FILE__,'calendar'); - $this->db->delete($this->dates_table,array('cal_id' => $cal_id,'cal_start' => $event['recur_exception']),__LINE__,__FILE__,'calendar'); - - $event['recur_exception'] = implode(',',$event['recur_exception']); + foreach($event['recur_exception'] as $recur_date) + { + // delete the execeptions from the user and dates table, it could be the first time + $this->db->delete($this->user_table,array('cal_id' => $cal_id,'cal_recur_date' => $recur_date),__LINE__,__FILE__,'calendar'); + $this->db->delete($this->dates_table,array('cal_id' => $cal_id,'cal_start' => $recur_date),__LINE__,__FILE__,'calendar'); + } } else { - $event['recur_exception'] = null; + $event['recur_exception'] = array(); } if (!$set_recurrences) { - // check if the recure-information changed + // check if the recurrence-information changed $old_recur = $this->db->select($this->repeats_table,'*',array('cal_id' => $cal_id),__LINE__,__FILE__,false,'','calendar')->fetch(); $old_exceptions = $old_recur['recur_exception'] ? explode(',',$old_recur['recur_exception']) : array(); - $exceptions = $event['recur_exception'] ? explode(',',$event['recur_exception']) : array(); $set_recurrences = $event['recur_type'] != $old_recur['recur_type'] || $event['recur_data'] != $old_recur['recur_data'] || - $event['recur_interval'] != $old_recur['recur_interval'] || $event['recur_enddate'] != $old_recur['recur_enddate'] || - count(array_diff($old_exceptions,$exceptions)); // exception deleted or added - } + (int)$event['recur_interval'] != (int)$old_recur['recur_interval'] || $event['recur_enddate'] != $old_recur['recur_enddate'] || + count(array_diff($old_exceptions,$event['recur_exception'])); // exception deleted or added + } + $event['recur_exception'] = empty($event['recur_exception']) ? null : implode(',',$event['recur_exception']); unset($event[0]); // unset the 'etag=etag+1', as it's not in the repeats table if($event['recur_type'] != MCAL_RECUR_NONE) { @@ -624,11 +626,13 @@ 'cal_id' => $cal_id, 'cal_start > '.(int)$min, ),__LINE__,__FILE__,'calendar'); + /* // delete all user-records, with recur-date != 0 $this->db->delete($this->user_table,array( 'cal_id' => $cal_id, 'cal_recur_date != 0', ),__LINE__,__FILE__,'calendar'); + */ } } // update start- and endtime if present in the event-array, evtl. we need to move all recurrences @@ -797,7 +801,6 @@ * @param int $cal_id * @param array $participants id => status pairs * @param int|boolean $change_since=0 false=new entry, > 0 time from which on the repetitions should be changed, default 0=all - * @param int $recur_date=0 time of which repetitions should be updated, default 0=all * @return int|boolean number of updated recurrences or false on error */ function participants($cal_id,$participants,$change_since=0) @@ -821,6 +824,7 @@ if ($change_since !== false) // existing entries only { // delete not longer set participants + $where[0] = 'cal_recur_date=0'; $deleted = array(); foreach($this->db->select($this->user_table,'DISTINCT cal_user_type,cal_user_id,cal_quantity',$where, __LINE__,__FILE__,false,'','calendar') as $row) @@ -838,6 +842,7 @@ unset($participants[$uid]); // we don't touch them } } + unset($where[0]); if (count($deleted)) { @@ -853,7 +858,7 @@ $this->db->delete($this->user_table,$where + array('('.implode(' OR ',$to_or).')'),__LINE__,__FILE__,'calendar'); } } - if (count($participants)) // these are NEW participants now + if (count($participants)) // these are ALL participants now - we exlude the existing ones later { // find all recurrences, as they all need the new parts to be added $recurrences = array(); @@ -866,8 +871,16 @@ } if (!count($recurrences)) $recurrences[] = 0; // insert the default one + $existing_participants = array(); // existing participants must not be updated + foreach($this->db->select($this->user_table,'DISTINCT cal_user_type,cal_user_id',$where,__LINE__,__FILE__,false,'','calendar') as $row) + { + $existing_participants[] = $this->combine_user($row['cal_user_type'],$row['cal_user_id']); + } + foreach($participants as $uid => $status) { + if (in_array($uid,$existing_participants)) continue; // don't update existing participants + $id = null; $this->split_user($uid,$type,$id); foreach($recurrences as $recur_date) @@ -936,7 +949,7 @@ ),$where,__LINE__,__FILE__,'calendar'); } $ret = $this->db->affected_rows(); - error_log(__METHOD__."($cal_id,$user_type,$user_id,$status,$recur_date) = $ret"); + //error_log(__METHOD__."($cal_id,$user_type,$user_id,$status,$recur_date) = $ret"); return $this->db->affected_rows(); } @@ -1218,6 +1231,12 @@ $user_type = $user_id = null; $this->split_user($uid, $user_type, $user_id); $participant_status = array(); + $where = array('cal_id' => $cal_id); + foreach($this->db->select($this->user_table,'DISTINCT cal_recur_date',$where,__LINE__,__FILE__,false,'','calendar') as $row) + { + // inititalize the array + $participant_status[$row['cal_recur_date']] = null; + } $where = array( 'cal_id' => $cal_id, 'cal_user_type' => $user_type ? $user_type : 'u', @@ -1251,15 +1270,8 @@ foreach ($this->db->select($this->user_table,'DISTINCT cal_user_type,cal_user_id', $where, __LINE__,__FILE__,false,'','calendar') as $row) { + $uid = $this->combine_user($row['cal_user_type'], $row['cal_user_id']); $id = $row['cal_user_type'] . $row['cal_user_id']; - if ($row['cal_user_type'] == 'u') - { - $uid = $row['cal_user_id']; - } - else - { - $uid = $id; - } $participants[$id]['type'] = $row['cal_user_type']; $participants[$id]['id'] = $row['cal_user_id']; $participants[$id]['uid'] = $uid; @@ -1292,4 +1304,48 @@ return $related; } + /** + * Gets the exception days of a given recurring event caused by + * irregular participant stati + * + * @param array $event Recurring Event. + * + * @return array Array of exception days (false for non-recurring events). + */ + function get_recurrence_exceptions(&$event) + { + $cal_id = (int) $event['id']; + if (!$cal_id || $event['recur_type'] == MCAL_RECUR_NONE) return false; + + $days = array(); + + $participants = $this->get_participants($event['id'], 0); + + // Check if the stati for all participants are identical for all recurrences + foreach ($participants as $uid => $attendee) + { + switch ($attendee['type']) + { + case 'u': // account + case 'c': // contact + case 'e': // email address + $recurrences = $this->get_recurrences($event['id'], $uid); + foreach ($recurrences as $recur_date => $recur_status) + { + if ($recur_date && $recur_status != $recurrences[0]) + { + // Every distinct status results in an exception + $days[] = $recur_date; + } + } + break; + default: // We don't handle the rest + break; + } + } + $days = array_unique($days); + sort($days); + return $days; + } + } Modified: branches/SyncML-1.2/calendar/inc/class.calendar_uiforms.inc.php URL: http://www.egroupware.org/viewvc/egroupware/branches/SyncML-1.2/calendar/inc/class.calendar_uiforms.inc.php?rev=27337&r1=27336&r2=27337&view=diff ============================================================================== --- branches/SyncML-1.2/calendar/inc/class.calendar_uiforms.inc.php (original) +++ branches/SyncML-1.2/calendar/inc/class.calendar_uiforms.inc.php Tue Jun 30 21:22:41 2009 @@ -451,11 +451,20 @@ { foreach ($event['participants'] as $uid => $status) { - if ($old_event['participants'][$uid][0] != $status) + if (!isset($old_event['participants'][$uid]) + || !$old_event['participants'][$uid] + || $old_event['participants'][$uid][0] != $status) { - $this->bo->set_status($old_event, $uid, $status, $content['edit_single']); + $this->bo->set_status($old_event, $uid, $status, $content['edit_single'], true); $unchanged = false; } + unset($old_event['participants'][$uid]); + } + foreach ($old_event['participants'] as $uid => $status) + { + // delete the removed participants + $this->bo->set_status($old_event, $uid, 'G', $content['edit_single'], true); + $unchanged = false; } if (!$unchanged) { |