forked from extern/egroupware
- started implementing virtual exceptions for AS (currently commented out in line 176, as not yet tested)
- ChangeMessage now searched contacts for participants and always re-adds resources (everything but accounts, contacts and email) - some more timezone specific fixes >>> none of the above is tested, as my iPhone charges no more and battery is now flat :-(
This commit is contained in:
parent
5e781b4135
commit
c65705aac4
@ -16,6 +16,22 @@
|
|||||||
* Calendar activesync plugin
|
* Calendar activesync plugin
|
||||||
*
|
*
|
||||||
* Plugin to make EGroupware calendar data available via Active Sync
|
* Plugin to make EGroupware calendar data available via Active Sync
|
||||||
|
*
|
||||||
|
* Handling of (virtual) exceptions of recurring events:
|
||||||
|
* ----------------------------------------------------
|
||||||
|
* Virtual exceptions are exceptions caused by recurcences with just different participant status
|
||||||
|
* compared to regular series (master). Real exceptions usually have different dates and/or other data.
|
||||||
|
* EGroupware calendar data model does NOT store virtual exceptions as exceptions,
|
||||||
|
* as participant status is stored per recurrence date and not just per event!
|
||||||
|
* GetMessageList reports virtual exceptions with an id like cal_id:recur_date, which
|
||||||
|
* is understood bei StatMessage, GetMessage and ChangeMessage (implementation is currently missing!).
|
||||||
|
* Real exceptions have there own calendar id, under which they are reported by GetMessageList.
|
||||||
|
*
|
||||||
|
* @todo alarms / reminders (currently they are not reported to and not changed by the device)
|
||||||
|
* We probably want to report only alarms of the current user (which should ring on the device)
|
||||||
|
* and save alarms set on the device only for the current user, if not yet there (preserving all other alarms).
|
||||||
|
* How to deal with multiple alarms allowed in EGroupware: report earliest one to the device
|
||||||
|
* (and hope it resyncs before next one is due, thought we do NOT report that as change currently!).
|
||||||
*/
|
*/
|
||||||
class calendar_activesync implements activesync_plugin_write
|
class calendar_activesync implements activesync_plugin_write
|
||||||
{
|
{
|
||||||
@ -139,7 +155,6 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
|
|
||||||
if (!$cutoffdate) $cutoffdate = $this->bo->now - 100*24*3600; // default three month back -30 breaks all sync recurrences
|
if (!$cutoffdate) $cutoffdate = $this->bo->now - 100*24*3600; // default three month back -30 breaks all sync recurrences
|
||||||
|
|
||||||
// @todo return only etag relevant information
|
|
||||||
$filter = array(
|
$filter = array(
|
||||||
'users' => $user,
|
'users' => $user,
|
||||||
'start' => $cutoffdate, // default one month back -30 breaks all sync recurrences
|
'start' => $cutoffdate, // default one month back -30 breaks all sync recurrences
|
||||||
@ -147,12 +162,26 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
'daywise' => false,
|
'daywise' => false,
|
||||||
'date_format' => 'server',
|
'date_format' => 'server',
|
||||||
'filter' => 'default', // not rejected
|
'filter' => 'default', // not rejected
|
||||||
|
// @todo return only etag relevant information (seems not to work ...)
|
||||||
|
//'cols' => array('egw_cal.cal_id', 'cal_start', 'recur_type', 'cal_modified', 'cal_uid', 'cal_etag'),
|
||||||
);
|
);
|
||||||
|
|
||||||
$messagelist = array();
|
$messagelist = array();
|
||||||
foreach ($this->calendar->search($filter) as $k => $event)
|
foreach ($this->calendar->search($filter) as $event)
|
||||||
{
|
{
|
||||||
$messagelist[] = $this->StatMessage($id, $event);
|
$messagelist[] = $this->StatMessage($id, $event);
|
||||||
|
|
||||||
|
// add virtual exceptions for recuring events too
|
||||||
|
// (we need to read event, as get_recurrence_exceptions need all infos!)
|
||||||
|
/* if ($event['recur_type'] != calendar_rrule::NONE)// && ($event = $this->calendar->read($event['id'],0,true,'server')))
|
||||||
|
{
|
||||||
|
|
||||||
|
foreach($this->calendar->so->get_recurrence_exceptions($event,
|
||||||
|
egw_time::$server_timezone->getName(), $cutoffdate, 0, 'all') as $recur_date)
|
||||||
|
{
|
||||||
|
$messagelist[] = $this->StatMessage($id, $event['id'].':'.$recur_date);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
return $messagelist;
|
return $messagelist;
|
||||||
}
|
}
|
||||||
@ -223,15 +252,24 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
|
|
||||||
debugLog (__METHOD__."('$folderid', $id, ".array2string($message).") type='$type', account=$account");
|
debugLog (__METHOD__."('$folderid', $id, ".array2string($message).") type='$type', account=$account");
|
||||||
|
|
||||||
if ($type != 'calendar' || $id && !($event = $this->calendar->read($id,null,false,'server')))
|
list($id,$recur_date) = explode(':',$id);
|
||||||
|
|
||||||
|
if ($type != 'calendar' || $id && !($event = $this->calendar->read($id, $recur_date, false, 'server')))
|
||||||
{
|
{
|
||||||
debugLog(__METHOD__."('$folderid',$id,...) Folder wrong or event does not existing");
|
debugLog(__METHOD__."('$folderid',$id,...) Folder wrong or event does not existing");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if ($recur_date) // virtual exception
|
||||||
|
{
|
||||||
|
// @todo check if virtual exception needs to be saved as real exception, or only stati need to be changed
|
||||||
|
debutLog(__METHOD__."('$folderid',$id:$recur_date,".array2string($message).") handling of virtual exception not yet implemented!");
|
||||||
|
error_log(__METHOD__."('$folderid',$id:$recur_date,".array2string($message).") handling of virtual exception not yet implemented!");
|
||||||
|
}
|
||||||
if (!$this->calendar->check_perms($id ? EGW_ACL_EDIT : EGW_ACL_ADD,$event ? $event : 0,$account))
|
if (!$this->calendar->check_perms($id ? EGW_ACL_EDIT : EGW_ACL_ADD,$event ? $event : 0,$account))
|
||||||
{
|
{
|
||||||
// @todo: write in users calendar and make account only a participant
|
// @todo: write in users calendar and make account only a participant
|
||||||
debugLog(__METHOD__."('$folderid',$id,...) no rights to add/edit event!");
|
debugLog(__METHOD__."('$folderid',$id,...) no rights to add/edit event!");
|
||||||
|
error_log(__METHOD__."('$folderid',$id,".array2string($message).") no rights to add/edit event!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// timestamps (created & modified are updated automatically)
|
// timestamps (created & modified are updated automatically)
|
||||||
@ -264,11 +302,28 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
$participants = array();
|
$participants = array();
|
||||||
foreach((array)$message->attendees as $attendee)
|
foreach((array)$message->attendees as $attendee)
|
||||||
{
|
{
|
||||||
|
if ($attendee->type == 3) continue; // we can not identify resources and re-add them anyway later
|
||||||
|
|
||||||
if (!($uid = $GLOBALS['egw']->accounts->name2id($attendee->email,'account_email')))
|
if (!($uid = $GLOBALS['egw']->accounts->name2id($attendee->email,'account_email')))
|
||||||
{
|
{
|
||||||
// @todo check for other resource types
|
$search = array(
|
||||||
|
'email' => $attendee->email,
|
||||||
|
'email_home' => $attendee->email,
|
||||||
|
//'n_fn' => $attendee->name, // not sure if we want matches without email
|
||||||
|
);
|
||||||
|
// search addressbook for participant
|
||||||
|
if ((list($data) = $this->addressbook->search($search,
|
||||||
|
array('id','egw_addressbook.account_id as account_id','n_fn'),
|
||||||
|
'egw_addressbook.account_id IS NOT NULL DESC, n_fn IS NOT NULL DESC',
|
||||||
|
'','',false,'OR')))
|
||||||
|
{
|
||||||
|
$uid = $data['account_id'] ? (int)$data['account_id'] : 'c'.$data['id'];
|
||||||
|
}
|
||||||
|
else // store just the email
|
||||||
|
{
|
||||||
$uid = 'e'.$attendee->name.' <'.$attendee->email.'>';
|
$uid = 'e'.$attendee->name.' <'.$attendee->email.'>';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!($status = array_search($attendee->status,self::$status2as))) $status = 'U';
|
if (!($status = array_search($attendee->status,self::$status2as))) $status = 'U';
|
||||||
if ($attendee->email == $message->organizeremail)
|
if ($attendee->email == $message->organizeremail)
|
||||||
{
|
{
|
||||||
@ -304,7 +359,19 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
$participants[$account] = calendar_so::combine_status($account == $GLOBALS['egw_info']['user']['account_id'] ?
|
$participants[$account] = calendar_so::combine_status($account == $GLOBALS['egw_info']['user']['account_id'] ?
|
||||||
'A' : 'U',1,!$chair_set ? 'CHAIR' : 'REQ-PARTICIPANT');
|
'A' : 'U',1,!$chair_set ? 'CHAIR' : 'REQ-PARTICIPANT');
|
||||||
}
|
}
|
||||||
// @todo: preserve all resource types not account, contact or email (eg. resources) for existing events
|
// preserve all resource types not account, contact or email (eg. resources) for existing events
|
||||||
|
foreach((array)$event['participant_types'] as $type => $parts)
|
||||||
|
{
|
||||||
|
if (in_array($type,array('u','c','e'))) continue; // they are correctly representable in AS
|
||||||
|
foreach($parts as $id => $status)
|
||||||
|
{
|
||||||
|
$uid = calendar_so::combine_user($type, $id);
|
||||||
|
if (!isset($participants[$uid]))
|
||||||
|
{
|
||||||
|
$participants[$uid] = $status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
$event['participants'] = $participants;
|
$event['participants'] = $participants;
|
||||||
|
|
||||||
if (isset($message->categories))
|
if (isset($message->categories))
|
||||||
@ -340,21 +407,21 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
}
|
}
|
||||||
if ($message->recurrence->until)
|
if ($message->recurrence->until)
|
||||||
{
|
{
|
||||||
$event['recur_enddate'] = $message->recurrence->until;
|
$event['recur_enddate'] = egw_time::server2user($message->recurrence->until);
|
||||||
}
|
}
|
||||||
$event['recur_exceptions'] = array();
|
$event['recur_exceptions'] = array();
|
||||||
if ($message->exceptions)
|
if ($message->exceptions)
|
||||||
{
|
{
|
||||||
foreach($message->exceptions as $exception)
|
foreach($message->exceptions as $exception)
|
||||||
{
|
{
|
||||||
$event['recur_exceptions'][] = $exception->starttime; // exceptions seems to be full SyncAppointments, with only starttime required
|
$event['recur_exceptions'][] = egw_time::server2user($exception->starttime); // exceptions seems to be full SyncAppointments, with only starttime required
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($message->recurrence->occurrences > 0)
|
if ($message->recurrence->occurrences > 0)
|
||||||
{
|
{
|
||||||
// calculate enddate from occurences count, as we only support enddate
|
// calculate enddate from occurences count, as we only support enddate
|
||||||
$count = $message->recurrence->occurrences;
|
$count = $message->recurrence->occurrences;
|
||||||
foreach(calendar_rrule::event2rrule($event,false) as $time) // false = timestamps in servertime
|
foreach(calendar_rrule::event2rrule($event, true) as $time) // true = timestamps are user time here, because of save!
|
||||||
{
|
{
|
||||||
if (--$count <= 0) break;
|
if (--$count <= 0) break;
|
||||||
}
|
}
|
||||||
@ -370,6 +437,7 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
debugLog(__METHOD__."('$folderid',$id,...) SUCESS saving event=".array2string($event).", id=$id");
|
debugLog(__METHOD__."('$folderid',$id,...) SUCESS saving event=".array2string($event).", id=$id");
|
||||||
|
error_log(__METHOD__."('$folderid',$id,".array2string($message).") SUCESS saving event=".array2string($event).", id=$id");
|
||||||
return $this->StatMessage($folderid, $id);
|
return $this->StatMessage($folderid, $id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,7 +533,8 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
|
|
||||||
debugLog (__METHOD__."('$folderid', $id, truncsize=$truncsize, bodyprefence=$bodypreference, mimesupport=$mimesupport)");
|
debugLog (__METHOD__."('$folderid', $id, truncsize=$truncsize, bodyprefence=$bodypreference, mimesupport=$mimesupport)");
|
||||||
$this->backend->splitID($folderid, $type, $account);
|
$this->backend->splitID($folderid, $type, $account);
|
||||||
if ($type != 'calendar' || !($event = $this->calendar->read($id,null,false,'server',$account)))
|
list($id,$recur_date) = explode(':',$id);
|
||||||
|
if ($type != 'calendar' || !($event = $this->calendar->read($id,$recur_date,false,'server',$account)))
|
||||||
{
|
{
|
||||||
error_log(__METHOD__."('$folderid', $id, ...) read($id,null,false,'server',$account) returned false");
|
error_log(__METHOD__."('$folderid', $id, ...) read($id,null,false,'server',$account) returned false");
|
||||||
return false;
|
return false;
|
||||||
@ -503,7 +572,7 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
$message->organizeremail = $GLOBALS['egw']->accounts->id2name($event['owner'],'account_email');
|
$message->organizeremail = $GLOBALS['egw']->accounts->id2name($event['owner'],'account_email');
|
||||||
|
|
||||||
$message->sensitivity = $event['public'] ? 0 : 2; // 0=normal, 1=personal, 2=private, 3=confidential
|
$message->sensitivity = $event['public'] ? 0 : 2; // 0=normal, 1=personal, 2=private, 3=confidential
|
||||||
$message->alldayevent = (int)$this->calendar->isWholeDay($event);
|
$message->alldayevent = (int)calendar_bo::isWholeDay($event);
|
||||||
|
|
||||||
$message->attendees = array();
|
$message->attendees = array();
|
||||||
foreach($event['participants'] as $uid => $status)
|
foreach($event['participants'] as $uid => $status)
|
||||||
@ -540,8 +609,8 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
$message->categories[] = categories::id2name($cat_id);
|
$message->categories[] = categories::id2name($cat_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// recurring information
|
// recurring information, only if not a single recurrence eg. virtual exception (!$recur_date)
|
||||||
if ($event['recur_type'] != calendar_rrule::NONE)
|
if ($event['recur_type'] != calendar_rrule::NONE && !$recur_date)
|
||||||
{
|
{
|
||||||
$message->recurrence = $recurrence = new SyncRecurrence();
|
$message->recurrence = $recurrence = new SyncRecurrence();
|
||||||
$rrule = calendar_rrule::event2rrule($event,false); // false = timestamps in $event are servertime
|
$rrule = calendar_rrule::event2rrule($event,false); // false = timestamps in $event are servertime
|
||||||
@ -577,6 +646,15 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
$message->exceptions[] = $exception;
|
$message->exceptions[] = $exception;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// add virtual exceptions here too (get_recurrence_exceptions should be able to return real-exceptions too!)
|
||||||
|
foreach($this->calendar->so->get_recurrence_exceptions($event,
|
||||||
|
egw_time::$server_timezone->getName(), $cutoffdate, 0, 'all') as $virtual_exception_time)
|
||||||
|
{
|
||||||
|
$exception = new SyncAppointment(); // exceptions seems to be full SyncAppointments, with only starttime required
|
||||||
|
$exception->starttime = $virtual_exception_time;
|
||||||
|
$message->exceptions[] = $exception;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
//$message->busystatus;
|
//$message->busystatus;
|
||||||
//$message->reminder;
|
//$message->reminder;
|
||||||
@ -608,19 +686,20 @@ class calendar_activesync implements activesync_plugin_write
|
|||||||
* time for this field, which will change as soon as the contents have changed.
|
* time for this field, which will change as soon as the contents have changed.
|
||||||
*
|
*
|
||||||
* @param string $folderid
|
* @param string $folderid
|
||||||
* @param int|array $id event id or array
|
* @param int|array $id event id or array or cal_id:recur_date for virtual exception
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function StatMessage($folderid, $id)
|
public function StatMessage($folderid, $id)
|
||||||
{
|
{
|
||||||
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,false))) // false: do NOT include exceptions into master etag
|
||||||
{
|
{
|
||||||
$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)
|
||||||
$backup = $this->calendar->debug;
|
$backup = $this->calendar->debug;
|
||||||
$this->calendar->debug = 2;
|
$this->calendar->debug = 2;
|
||||||
|
list($id) = explode(':',$id);
|
||||||
$this->calendar->check_perms(EGW_ACL_FREEBUSY, $id, 0, 'server');
|
$this->calendar->check_perms(EGW_ACL_FREEBUSY, $id, 0, 'server');
|
||||||
$this->calendar->debug = $backup;
|
$this->calendar->debug = $backup;
|
||||||
}
|
}
|
||||||
|
@ -769,7 +769,7 @@ class calendar_bo
|
|||||||
// (this will fail on 32bit systems for times > 2038!)
|
// (this will fail on 32bit systems for times > 2038!)
|
||||||
$event['start'] = (int)$event['start']; // this is for isWholeDay(), which also calls egw_time
|
$event['start'] = (int)$event['start']; // this is for isWholeDay(), which also calls egw_time
|
||||||
$event['end'] = (int)$event['end'];
|
$event['end'] = (int)$event['end'];
|
||||||
$event['whole_day'] = $this->isWholeDay($event);
|
$event['whole_day'] = self::isWholeDay($event);
|
||||||
if ($event['whole_day'] && $date_format != 'server')
|
if ($event['whole_day'] && $date_format != 'server')
|
||||||
{
|
{
|
||||||
// Adjust dates to user TZ
|
// Adjust dates to user TZ
|
||||||
@ -1857,19 +1857,19 @@ class calendar_bo
|
|||||||
* @param array $event event
|
* @param array $event event
|
||||||
* @return boolean true if whole day event, false othwerwise
|
* @return boolean true if whole day event, false othwerwise
|
||||||
*/
|
*/
|
||||||
function isWholeDay($event)
|
public static function isWholeDay($event)
|
||||||
{
|
{
|
||||||
// check if the event is the whole day
|
// check if the event is the whole day
|
||||||
$start = $this->date2array($event['start']);
|
$start = self::date2array($event['start']);
|
||||||
$end = $this->date2array($event['end']);
|
$end = self::date2array($event['end']);
|
||||||
|
|
||||||
return !$start['hour'] && !$start['minute'] && $end['hour'] == 23 && $end['minute'] == 59;
|
return !$start['hour'] && !$start['minute'] && $end['hour'] == 23 && $end['minute'] == 59;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* @param array|int|string $event array with event or cal_id, or cal_id:recur_date for virtual exceptions
|
||||||
* @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
|
||||||
* @return string|boolean string with etag or false
|
* @return string|boolean string with etag or false
|
||||||
*/
|
*/
|
||||||
@ -1877,8 +1877,9 @@ class calendar_bo
|
|||||||
{
|
{
|
||||||
if (!is_array($entry))
|
if (!is_array($entry))
|
||||||
{
|
{
|
||||||
|
list($entry,$recur_date) = explode(':',$entry);
|
||||||
if (!$this->check_perms(EGW_ACL_FREEBUSY, $entry, 0, 'server')) return false;
|
if (!$this->check_perms(EGW_ACL_FREEBUSY, $entry, 0, 'server')) return false;
|
||||||
$entry = $this->read($entry, null, true, 'server');
|
$entry = $this->read($entry, $recur_date, true, 'server');
|
||||||
}
|
}
|
||||||
$etag = $entry['id'].':'.$entry['etag'];
|
$etag = $entry['id'].':'.$entry['etag'];
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user