* CalDAV/Calendar: storing now all properties send by client and not known to EGroupware and fixed acknowledging and snoozing of alarms

This commit is contained in:
Ralf Becker 2015-06-25 20:39:53 +00:00
parent b8192fec8c
commit 6d2ef17b0f
4 changed files with 308 additions and 238 deletions

View File

@ -6,7 +6,7 @@
* @package calendar
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) 2005-12 by RalfBecker-At-outdoor-training.de
* @copyright (c) 2005-15 by RalfBecker-At-outdoor-training.de
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
@ -92,11 +92,12 @@ class calendar_boupdate extends calendar_bo
* updates or creates an event, it (optionaly) checks for conflicts and sends the necessary notifications
*
* @param array &$event event-array, on return some values might be changed due to set defaults
* @param boolean $ignore_conflicts=false just ignore conflicts or do a conflict check and return the conflicting events
* @param boolean $touch_modified=true NOT USED ANYMORE (was only used in old csv-import), modified&modifier is always updated!
* @param boolean $ignore_acl=false should we ignore the acl
* @param boolean $updateTS=true update the content history of the event
* @param boolean $ignore_conflicts =false just ignore conflicts or do a conflict check and return the conflicting events
* @param boolean $touch_modified =true NOT USED ANYMORE (was only used in old csv-import), modified&modifier is always updated!
* @param boolean $ignore_acl =false should we ignore the acl
* @param boolean $updateTS =true update the content history of the event
* @param array &$messages=null messages about because of missing ACL removed participants or categories
* @param boolean $skip_notification =false true: send NO notifications, default false = send them
* @return mixed on success: int $cal_id > 0, on error false or array with conflicting events (only if $check_conflicts)
* Please note: the events are not garantied to be readable by the user (no read grant or private)!
*
@ -141,8 +142,7 @@ class calendar_boupdate extends calendar_bo
// set owner as participant if none is given
if (!is_array($event['participants']) || !count($event['participants']))
{
$status = $event['owner'] == $this->user ? 'A' : 'U';
$status = calendar_so::combine_status($status, 1, 'CHAIR');
$status = calendar_so::combine_status($event['owner'] == $this->user ? 'A' : 'U', 1, 'CHAIR');
$event['participants'] = array($event['owner'] => $status);
}
}
@ -205,6 +205,7 @@ class calendar_boupdate extends calendar_bo
{
foreach((array)$event['participants'] as $uid => $status)
{
$q = $r = null;
calendar_so::split_status($status,$q,$r);
if ($status != 'U')
{
@ -399,7 +400,7 @@ class calendar_boupdate extends calendar_bo
* Remove participants current user has no right to invite
*
* @param array &$event new event
* @param array $old_event=null old event with already invited participants
* @param array $old_event =null old event with already invited participants
* @return array removed participants because of missing invite grants
*/
public function remove_no_acl_invite(array &$event,array $old_event=null)
@ -413,7 +414,7 @@ class calendar_boupdate extends calendar_bo
$old_event = $this->read($event['id']);
}
$removed = array();
foreach((array)$event['participants'] as $uid => $status)
foreach(array_keys((array)$event['participants']) as $uid)
{
if ((is_null($old_event) || !isset($old_event['participants'][$uid])) && !$this->check_acl_invite($uid))
{
@ -479,7 +480,7 @@ class calendar_boupdate extends calendar_bo
}
}
// Find new participants ...
foreach((array)$new_event['participants'] as $new_userid => $new_status)
foreach(array_keys((array)$new_event['participants']) as $new_userid)
{
if(!isset($old_event['participants'][$new_userid]))
{
@ -590,8 +591,8 @@ class calendar_boupdate extends calendar_bo
* Check calendar prefs, if a given user (integer account_id) or email (user or externals) should get notified
*
* @param int|string $user_or_email
* @param string $ical_method='REQUEST'
* @param string $role='REQ-PARTICIPANT'
* @param string $ical_method ='REQUEST'
* @param string $role ='REQ-PARTICIPANT'
* @return boolean true if user requested to be notified, false if not
*/
static public function email_update_requested($user_or_email, $ical_method='REQUEST', $role='REQ-PARTICIPANT')
@ -710,8 +711,8 @@ class calendar_boupdate extends calendar_bo
* @param int $msg_type type of the notification: MSG_ADDED, MSG_MODIFIED, MSG_ACCEPTED, ...
* @param array $to_notify numerical user-ids as keys (!) (value is not used)
* @param array $old_event Event before the change
* @param array $new_event=null Event after the change
* @param int $user=0 User who started the notify, default current user
* @param array $new_event =null Event after the change
* @param int $user =0 User who started the notify, default current user
* @return bool true/false
*/
function send_update($msg_type,$to_notify,$old_event,$new_event=null,$user=0)
@ -1004,6 +1005,7 @@ class calendar_boupdate extends calendar_bo
function get_update_message($event,$added)
{
$nul = null;
$details = $this->_get_event_details($event,$added ? lang('Added') : lang('Modified'),$nul);
$notify_msg = $this->cal_prefs[$added || empty($this->cal_prefs['notifyModified']) ? 'notifyAdded' : 'notifyModified'];
@ -1051,7 +1053,8 @@ class calendar_boupdate extends calendar_bo
$alarm['time'] = $this->date2ts($event['start']) - $alarm['offset'];
unset($alarm['times']);
unset($alarm['next']);
$this->save_alarm($alarm['cal_id'],$alarm);
//error_log(__METHOD__."() moving alarm to next recurrence ".array2string($alarm));
$this->save_alarm($alarm['cal_id'], $alarm, false); // false = do NOT update timestamp, as nothing changed for iCal clients
}
return $ret;
}
@ -1062,8 +1065,8 @@ class calendar_boupdate extends calendar_bo
* This methode converts from user to server time and handles the insertion of users and dates of repeating events
*
* @param array $event
* @param boolean $ignore_acl=false should we ignore the acl
* @param boolean $updateTS=true update the content history of the event
* @param boolean $ignore_acl =false should we ignore the acl
* @param boolean $updateTS =true update the content history of the event
* @return int|boolean $cal_id > 0 or false on error (eg. permission denied)
*/
function save($event,$ignore_acl=false,$updateTS=true)
@ -1095,8 +1098,7 @@ class calendar_boupdate extends calendar_bo
{
if (!empty($event['start']))
{
$time = new egw_time($event['start'], egw_time::$user_timezone);
$time = $this->so->startOfDay($time);
$time = $this->so->startOfDay(new egw_time($event['start'], egw_time::$user_timezone));
$event['start'] = egw_time::to($time, 'ts');
$save_event['start'] = $time;
}
@ -1109,14 +1111,12 @@ class calendar_boupdate extends calendar_bo
}
if (!empty($event['recurrence']))
{
$time = new egw_time($event['recurrence'], egw_time::$user_timezone);
$time = $this->so->startOfDay($time);
$time = $this->so->startOfDay(new egw_time($event['recurrence'], egw_time::$user_timezone));
$event['recurrence'] = egw_time::to($time, 'ts');
}
if (!empty($event['recur_enddate']))
{
$time = new egw_time($event['recur_enddate'], egw_time::$user_timezone);
$time = $this->so->startOfDay($time);
$time = $this->so->startOfDay(new egw_time($event['recur_enddate'], egw_time::$user_timezone));
$event['recur_enddate'] = egw_time::to($time, 'ts');
$time->setUser();
$save_event['recur_enddate'] = egw_time::to($time, 'ts');
@ -1147,8 +1147,7 @@ class calendar_boupdate extends calendar_bo
{
if ($event['whole_day'])
{
$time = new egw_time($date, egw_time::$user_timezone);
$time =& $this->so->startOfDay($time);
$time = $this->so->startOfDay(new egw_time($date, egw_time::$user_timezone));
$date = egw_time::to($time, 'ts');
}
else
@ -1161,11 +1160,8 @@ class calendar_boupdate extends calendar_bo
// same with the alarms
if (isset($event['alarm']) && is_array($event['alarm']) && isset($event['start']))
{
foreach($event['alarm'] as $id => $alarm)
foreach($event['alarm'] as $id => &$alarm)
{
// recalculate alarms to also cope with moved events (beside server time adjustment)
$event['alarm'][$id]['time'] = $event['start'] - $alarm['offset'];
// remove alarms belonging to not longer existing or rejected participants
if ($alarm['owner'] && isset($event['participants']))
{
@ -1182,18 +1178,19 @@ class calendar_boupdate extends calendar_bo
// update all existing alarm times, in case alarm got moved and alarms are not include in $event
if ($old_event && is_array($old_event['alarm']) && isset($event['start']))
{
foreach($old_event['alarm'] as $id => $alarm)
foreach($old_event['alarm'] as $id => &$alarm)
{
if (!isset($event['alarm'][$id]))
{
$alarm['time'] = $event['start'] - $alarm['offset'];
// remove (not store) alarms belonging to not longer existing or rejected participants
if ($alarm['time'] < time()) calendar_so::shift_alarm($event, $alarm);
// remove (not store) alarms belonging to not longer existing or rejected participants
$status = isset($event['participants']) ? $event['participants'][$alarm['owner']] :
$old_event['participants'][$alarm['owner']];
if (!$alarm['owner'] || isset($status) && calendar_so::split_status($status) !== 'R')
{
$this->so->save_alarm($event['id'], $alarm, $this->now);
error_log(__LINE__.': '.__METHOD__."() so->save_alarm($event[id], ".array2string($alarm).", $this->now)");
$this->so->save_alarm($event['id'], $alarm);
error_log(__LINE__.': '.__METHOD__."() so->save_alarm($event[id], ".array2string($alarm).")");
}
else
{
@ -1229,6 +1226,7 @@ class calendar_boupdate extends calendar_bo
{
foreach($event['participants'] as $uid => $status)
{
$user_type = $user_id = null;
calendar_so::split_user($uid, $user_type, $user_id);
if ($user_type == 'c' && (!$old_event || !isset($old_event['participants'][$uid])))
{
@ -1332,7 +1330,7 @@ class calendar_boupdate extends calendar_bo
/**
* Get rights for a given category id
*
* @param int $cat_id=null null to return array with all cats
* @param int $cat_id =null null to return array with all cats
* @return array with account_id => right pairs
*/
public static function get_cat_rights($cat_id=null)
@ -1384,7 +1382,7 @@ class calendar_boupdate extends calendar_bo
*/
public static function has_cat_right($right,$cat_id,$user)
{
static $cache;
static $cache=null;
if (!isset($cache[$cat_id]))
{
@ -1392,7 +1390,7 @@ class calendar_boupdate extends calendar_bo
$cat_rights = self::get_cat_rights($cat_id);
if (!is_null($cat_rights))
{
static $memberships;
static $memberships=null;
if (is_null($memberships))
{
$memberships = $GLOBALS['egw']->accounts->memberships($user,true);
@ -1432,6 +1430,7 @@ class calendar_boupdate extends calendar_bo
{
return false;
}
$quantity = $role = null;
calendar_so::split_status($status, $quantity, $role);
if ($this->log)
{
@ -1486,7 +1485,7 @@ class calendar_boupdate extends calendar_bo
*
* @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
* @param int $recur_date =0 date to change, or 0 = all since now
* @param boolean $skip_notification Do not send notifications. Parameter passed on to set_status().
*/
function update_status($new_event, $old_event , $recur_date=0, $skip_notification=false)
@ -1517,10 +1516,10 @@ class calendar_boupdate extends calendar_bo
* deletes an event
*
* @param int $cal_id id of the event to delete
* @param int $recur_date=0 if a single event from a series should be deleted, its date
* @param boolean $ignore_acl=false true for no ACL check, default do ACL check
* @param boolean $skip_notification=false
* @param boolean $delete_exceptions=true true: delete, false: keep exceptions (with new UID)
* @param int $recur_date =0 if a single event from a series should be deleted, its date
* @param boolean $ignore_acl =false true for no ACL check, default do ACL check
* @param boolean $skip_notification =false
* @param boolean $delete_exceptions =true true: delete, false: keep exceptions (with new UID)
* @param int &$exceptions_kept=null on return number of kept exceptions
* @return boolean true on success, false on error (usually permission denied)
*/
@ -1594,6 +1593,7 @@ class calendar_boupdate extends calendar_bo
// check if deleted recurrance has alarms (because it's the next recurrance) --> move it to next recurrance
if ($event['alarm'])
{
$next_recurrance = null;
foreach($event['alarm'] as &$alarm)
{
if (($alarm['time'] == $recur_date) || ($alarm['time']+$alarm['offset'] == $recur_date))
@ -1604,19 +1604,10 @@ class calendar_boupdate extends calendar_bo
{
$checkdate = $recur_date;
//if ($alarm['time']+$alarm['offset'] == $recur_date) $checkdate = $recur_date + $alarm['offset'];
if ($e = $this->read($cal_id,$checkdate+1))
if (($e = $this->read($cal_id,$checkdate+1)))
{
$next_recurrance = $this->date2ts($e['start']);
}
/*
foreach(calendar_rrule::event2rrule($event, true) as $time)
{
$time->setUser(); // $time is in timezone of event, convert it to usertime used here
$next_recurrance = $this->date2ts($time);
if ($next_recurrance > $checkdate) break;
}
*/
//error_log(__METHOD__.__LINE__." $next_recurrance > $checkdate");
}
$alarm['time'] = $this->date2ts($next_recurrance, true); // user to server-time
$alarm['cal_id'] = $cal_id;
@ -1644,6 +1635,11 @@ class calendar_boupdate extends calendar_bo
/**
* helper for send_update and get_update_message
* @internal
* @param array $event
* @param string $action
* @param array $event_arr
* @param array $disinvited
* @return array
*/
function _get_event_details($event,$action,&$event_arr,$disinvited=array())
{
@ -1787,8 +1783,8 @@ class calendar_boupdate extends calendar_bo
*
* @param array $event2save event-data before calling save
* @param array $event_saved event-data read back from the DB
* @param array $old_event=null event-data in the DB before calling save
* @param string $type='update'
* @param array $old_event =null event-data in the DB before calling save
* @param string $type ='update'
*/
function log2file($event2save,$event_saved,$old_event=null,$type='update')
{
@ -1820,9 +1816,10 @@ class calendar_boupdate extends calendar_bo
*
* @param int $cal_id Id of the calendar-entry
* @param array $alarm array with fields: text, owner, enabled, ..
* @param boolean $update_modified =true call update modified, default true
* @return string id of the alarm, or false on error (eg. no perms)
*/
function save_alarm($cal_id,$alarm)
function save_alarm($cal_id, $alarm, $update_modified=true)
{
if (!$cal_id || !$this->check_perms(EGW_ACL_EDIT,$alarm['all'] ? $cal_id : 0,!$alarm['all'] ? $alarm['owner'] : 0))
{
@ -1833,7 +1830,7 @@ class calendar_boupdate extends calendar_bo
$GLOBALS['egw']->contenthistory->updateTimeStamp('calendar', $cal_id, 'modify', $this->now);
return $this->so->save_alarm($cal_id,$alarm);
return $this->so->save_alarm($cal_id, $alarm, $update_modified);
}
/**
@ -1861,7 +1858,7 @@ class calendar_boupdate extends calendar_bo
* currently used for ical/sif import
*
* @param array $catname_list names of the categories which should be found or added
* @param int|array $old_event=null match against existing event and expand the returned category ids
* @param int|array $old_event =null match against existing event and expand the returned category ids
* by the ones the user normally does not see due to category permissions - used to preserve categories
* @return array category ids (found, added and preserved categories)
*/
@ -2191,7 +2188,7 @@ class calendar_boupdate extends calendar_bo
if ($filter == 'master')
{
// We found the master
$matchingEvents = array($egwEvent['id']);;
$matchingEvents = array($egwEvent['id']);
break;
}
if ($filter == 'exact')
@ -2446,14 +2443,14 @@ class calendar_boupdate extends calendar_bo
}
// append pseudos as last entries
$matchingEvents = array_merge($matchingEvents, $pseudos);
$matches = array_merge($matchingEvents, $pseudos);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
'[MATCHES]:' . array2string($matchingEvents)."\n",3,$this->logfile);
'[MATCHES]:' . array2string($matches)."\n",3,$this->logfile);
}
return $matchingEvents;
return $matches;
}
/**
@ -2493,8 +2490,8 @@ class calendar_boupdate extends calendar_bo
{
$type = 'SERIES-PSEUDO-EXCEPTION';
$wasPseudo = true;
list($eventID, $recur_date) = explode(':', $eventID);
$recur_date = $this->date2usertime($recur_date);
list($eventID, $date) = explode(':', $eventID);
$recur_date = $this->date2usertime($date);
$stored_event = $this->read($eventID, $recur_date, false, 'server');
$master_event = $this->read($eventID, 0, false, 'server');
$recurrence_event = $stored_event;

View File

@ -7,7 +7,7 @@
* @package calendar
* @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-13 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-15 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
@ -135,7 +135,7 @@ class calendar_groupdav extends groupdav_handler
* @param array &$options
* @param array &$files
* @param int $user account_id
* @param string $id=''
* @param string $id =''
* @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
*/
function propfind($path,&$options,&$files,$user,$id='')
@ -157,6 +157,7 @@ class calendar_groupdav extends groupdav_handler
'daywise' => false,
'date_format' => 'server',
'no_total' => true, // we need no total number of rows (saves extra query)
'cfs' => array(), // return custom-fields, as we use them to store X- attributes
);
foreach(array(
'start' => $GLOBALS['egw_info']['user']['preferences']['groupdav']['calendar-past-limit'],
@ -252,7 +253,7 @@ class calendar_groupdav extends groupdav_handler
*
* @param string $path
* @param array $filter
* @param array|boolean $start=false false=return all or array(start,num)
* @param array|boolean $start =false false=return all or array(start,num)
* @return array with "files" array with values for keys path and props
*/
function propfind_callback($path,array $filter,$start=false)
@ -289,6 +290,7 @@ class calendar_groupdav extends groupdav_handler
$files[] = array('path' => $path.urldecode($this->get_path($event)));
continue;
}
$schedule_tag = null;
$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']));
@ -555,7 +557,7 @@ class calendar_groupdav extends groupdav_handler
*
* @param array &$options
* @param int $id
* @param int $user=null account_id
* @param int $user =null account_id
* @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
*/
function get(&$options,$id,$user=null)
@ -568,6 +570,7 @@ class calendar_groupdav extends groupdav_handler
$options['data'] = $this->iCal($event, $user, strpos($options['path'], '/inbox/') !== false ? 'REQUEST' : null);
$options['mimetype'] = 'text/calendar; charset=utf-8';
header('Content-Encoding: identity');
$schedule_tag = null;
header('ETag: "'.$this->get_etag($event, $schedule_tag).'"');
if ($this->use_schedule_tag)
{
@ -582,9 +585,9 @@ class calendar_groupdav extends groupdav_handler
* Taking into account virtual an real exceptions for recuring events
*
* @param array $event
* @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 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
* @return string
*/
private function iCal(array $event,$user=null, $method=null, $expand=false)
@ -626,9 +629,9 @@ class calendar_groupdav extends groupdav_handler
* Maybe that should be part of calendar_bo
*
* @param string $uid UID
* @param calendar_bo $bo=null calendar_bo object to reuse for search call
* @param boolean|array $expand=false true or array with values for 'start', 'end' to expand recurrences
* @param int $user=null account_id of calendar to display, to remove master, if current user does not participate in
* @param calendar_bo $bo =null calendar_bo object to reuse for search call
* @param boolean|array $expand =false true or array with values for 'start', 'end' to expand recurrences
* @param int $user =null account_id of calendar to display, to remove master, if current user does not participate in
* @return array
*/
private static function &get_series($uid,calendar_bo $bo=null, $expand=false, $user=null)
@ -640,6 +643,7 @@ class calendar_groupdav extends groupdav_handler
'filter' => 'owner', // return all possible entries
'daywise' => false,
'date_format' => 'server',
'cfs' => array(), // read cfs as we use them to store X- attributes
);
if (is_array($expand)) $params += $expand;
@ -690,6 +694,13 @@ class calendar_groupdav extends groupdav_handler
}
continue; // nothing to change
}
// alarms are reported on recurrences --> move them to master
foreach($recurrence['alarm'] as $alarm)
{
$master['alarm'][] = $alarm;
}
$recurrence['alarm'] = array();
// now we need to check if this recurrence is an exception
if (!$expand && $master['participants'] == $recurrence['participants'])
{
@ -731,8 +742,8 @@ class calendar_groupdav extends groupdav_handler
*
* @param array &$options
* @param int $id
* @param int $user=null account_id of owner, default null
* @param string $prefix=null user prefix from path (eg. /ralf from /ralf/addressbook)
* @param int $user =null account_id of owner, default null
* @param string $prefix =null user prefix from path (eg. /ralf from /ralf/addressbook)
* @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
*/
function put(&$options,$id,$user=null,$prefix=null)
@ -811,6 +822,7 @@ class calendar_groupdav extends groupdav_handler
{
$schedule_tag_match = $_SERVER['HTTP_IF_SCHEDULE_TAG_MATCH'];
if ($schedule_tag_match[0] == '"') $schedule_tag_match = substr($schedule_tag_match, 1, -1);
$schedule_tag = null;
$this->get_etag($oldEvent, $schedule_tag);
if ($schedule_tag_match !== $schedule_tag)
@ -835,6 +847,7 @@ class calendar_groupdav extends groupdav_handler
if (($events = $handler->icaltoegw($vCalendar)))
{
$modified = 0;
$series = null;
foreach($events as $n => $event)
{
// for recurrances of event series, we need to read correct recurrence (or if series master is no first event)
@ -883,7 +896,8 @@ class calendar_groupdav extends groupdav_handler
// import alarms, if given and changed
if ((array)$event['alarm'] !== (array)$oldEvent['alarm'])
{
$modified += $this->sync_alarms($oldEvent['id'], (array)$event['alarm'], (array)$oldEvent['alarm'], $user, $event['start']);
$event['id'] = $oldEvent['id'];
$modified += $handler->sync_alarms($event, (array)$oldEvent['alarm'], $user);
}
}
if (!$modified) // NO modififictions, or none we understood --> log it and return Ok: "204 No Content"
@ -951,59 +965,12 @@ class calendar_groupdav extends groupdav_handler
return $retval;
}
/**
* Sync alarms of current user: add alarms added on client and remove the ones removed
*
* @param int $cal_id of event to set alarms
* @param array $alarms
* @param array $old_alarms
* @param int $user account_id of user to create alarm for
* @param int $start start-time of event
* @ToDo store other alarm properties like: ACTION, DESCRIPTION, X-WR-ALARMUID
* @return int number of modified alarms
*/
private function sync_alarms($cal_id, array $alarms, array $old_alarms, $user, $start)
{
if ($this->debug) error_log(__METHOD__."($cal_id, ".array2string($alarms).', '.array2string($old_alarms).", $user, $start)");
$modified = 0;
foreach($alarms as $alarm)
{
if ($alarm['owner'] != $this->user) continue; // only import alarms of current user
// check if alarm is already stored or from other users
foreach($old_alarms as $id => $old_alarm)
{
if ($old_alarm['owner'] != $user || $alarm['offset'] == $old_alarm['offset'])
{
unset($old_alarms[$id]); // remove alarms of other user, or already existing alarms
}
}
// alarm not found --> add it
if ($alarm['offset'] != $old_alarm['offset'] || $old_alarm['owner'] != $user)
{
$alarm['owner'] = $user;
$alarm['time'] = $start - $alarm['offset'];
if ($this->debug) error_log(__METHOD__."() adding new alarm from client ".array2string($alarm));
$this->bo->save_alarm($cal_id, $alarm);
++$modified;
}
}
// remove all old alarms left from current user
foreach($old_alarms as $id => $old_alarm)
{
if ($this->debug) error_log(__METHOD__."() deleting alarm '$id' deleted on client ".array2string($old_alarm));
$this->bo->delete_alarm($id);
++$modified;
}
return $modified;
}
/**
* Handle post request for a schedule entry
*
* @param array &$options
* @param int $id
* @param int $user=null account_id of owner, default null
* @param int $user =null account_id of owner, default null
* @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
*/
function post(&$options,$id,$user=null)
@ -1057,8 +1024,8 @@ class calendar_groupdav extends groupdav_handler
$handler = $this->_get_handler();
if (($foundEvents = $handler->search($vCalendar, null, false, $charset)))
{
$eventId = array_shift($foundEvents);
list($eventId) = explode(':', $eventId);
$id = array_shift($foundEvents);
list($eventId) = explode(':', $id);
if (!($cal_id = $handler->importVCal($vCalendar, $eventId, null,
false, 0, $this->groupdav->current_user_principal, $user, $charset)))
@ -1082,6 +1049,8 @@ class calendar_groupdav extends groupdav_handler
*/
protected function outbox_freebusy_request($ical, $charset, $user, array &$options)
{
unset($options); // not used, but required by function signature
$vcal = new Horde_Icalendar();
if (!$vcal->parsevCalendar($ical, 'VCALENDAR', $charset))
{
@ -1108,7 +1077,7 @@ class calendar_groupdav extends groupdav_handler
$organizer = $component->getAttribute('ORGANIZER');
$attendees = (array)$component->getAttribute('ATTENDEE');
// X-CALENDARSERVER-MASK-UID specifies to exclude given event from busy-time
$mask_uid = $component->getAttribute('X-CALENDARSERVER-MASK-UID');
$mask_uid = $component->getAttributeDefault('X-CALENDARSERVER-MASK-UID', null);
header('Content-type: text/xml; charset=UTF-8');
@ -1118,7 +1087,7 @@ class calendar_groupdav extends groupdav_handler
$xml->startDocument('1.0', 'UTF-8');
$xml->startElementNs('C', 'schedule-response', groupdav::CALDAV);
foreach($event['participants'] as $uid => $status)
foreach(array_keys($event['participants']) as $uid)
{
$xml->startElementNs('C', 'response', null);
@ -1155,6 +1124,7 @@ class calendar_groupdav extends groupdav_handler
*/
function free_busy_report($path,$options,$user)
{
unset($path); // unused, but required by function signature
if (!$this->bo->check_perms(EGW_ACL_FREEBUSY, 0, $user))
{
return '403 Forbidden';
@ -1180,7 +1150,7 @@ class calendar_groupdav extends groupdav_handler
* Reimplemented to add read-free-busy and schedule-deliver privilege
*
* @param string $path path of collection
* @param int $user=null owner of the collection, default current user
* @param int $user =null owner of the collection, default current user
* @return array with privileges
*/
public function current_user_privileges($path, $user=null)
@ -1306,7 +1276,7 @@ class calendar_groupdav extends groupdav_handler
// check if user is a participant or one of the groups he is a member of --> reject the meeting request
$ret = '403 Forbidden';
$memberships = $GLOBALS['egw']->accounts->memberships($this->bo->user, true);
foreach($event['participants'] as $uid => $status)
foreach(array_keys($event['participants']) as $uid)
{
if ($this->bo->user == $uid || in_array($uid, $memberships))
{
@ -1391,7 +1361,8 @@ class calendar_groupdav extends groupdav_handler
/**
* Get the etag for an entry
*
* @param array|int $event array with event or cal_id
* @param array|int $entry array with event or cal_id
* @param string $schedule_tag =null on return schedule-tag
* @return string|boolean string with etag or false
*/
function get_etag($entry, &$schedule_tag=null)
@ -1410,10 +1381,11 @@ class calendar_groupdav extends groupdav_handler
* @param int|array $entry id or array of new created entry
* @param string $path
* @param int|string $retval
* @param boolean $path_attr_is_name=true true: path_attr is ca(l|rd)dav_name, false: id (GroupDAV needs Location header)
* @param boolean $path_attr_is_name =true true: path_attr is ca(l|rd)dav_name, false: id (GroupDAV needs Location header)
*/
function put_response_headers($entry, $path, $retval, $path_attr_is_name=true)
{
$schedule_tag = null;
$etag = $this->get_etag($entry, $schedule_tag);
if ($this->use_schedule_tag)
@ -1443,15 +1415,16 @@ class calendar_groupdav extends groupdav_handler
/**
* Add extra properties for calendar collections
*
* @param array $props=array() regular props by the groupdav handler
* @param array $props regular props by the groupdav handler
* @param string $displayname
* @param string $base_uri=null base url of handler
* @param int $user=null account_id of owner of current collection
* @param string $path=null path of the collection
* @param string $base_uri =null base url of handler
* @param int $user =null account_id of owner of current collection
* @param string $path =null path of the collection
* @return array
*/
public function extra_properties(array $props=array(), $displayname, $base_uri=null, $user=null, $path=null)
public function extra_properties(array $props, $displayname, $base_uri=null, $user=null, $path=null)
{
unset($base_uri); // unused, but required by function signature
if (!isset($props['calendar-description']))
{
// default calendar description: can be overwritten via PROPPATCH, in which case it's already set
@ -1523,8 +1496,8 @@ class calendar_groupdav extends groupdav_handler
function get_shared()
{
$shared = array();
$calendar_home_set = $GLOBALS['egw_info']['user']['preferences']['groupdav']['calendar-home-set'];
$calendar_home_set = $calendar_home_set ? explode(',',$calendar_home_set) : array();
$pref = $GLOBALS['egw_info']['user']['preferences']['groupdav']['calendar-home-set'];
$calendar_home_set = $pref ? explode(',', $pref) : array();
// replace symbolic id's with real nummeric id's
foreach(array(
'G' => $GLOBALS['egw_info']['user']['account_primary_group'],

View File

@ -251,9 +251,9 @@ class calendar_ical extends calendar_boupdate
{
if ($this->read($event, $recurrence, true, 'server'))
{
if ($this->bo->check_perms(EGW_ACL_FREEBUSY, $event, 0, 'server'))
if ($this->check_perms(EGW_ACL_FREEBUSY, $event, 0, 'server'))
{
$this->bo->clear_private_infos($event, array($this->user, $event['owner']));
$this->clear_private_infos($event, array($this->user, $event['owner']));
}
else
{
@ -631,7 +631,10 @@ class calendar_ical extends calendar_boupdate
else // $version == '2.0'
{
$attributes['RRULE'] = '';
$parameters['RRULE'] = $rrule;
foreach($rrule as $n => $v)
{
$attributes['RRULE'] .= ($attributes['RRULE']?';':'').$n.'='.$v;
}
}
break;
@ -833,6 +836,25 @@ class calendar_ical extends calendar_boupdate
}
}
// for CalDAV add all X-Properties previously parsed
if ($this->productManufacturer == 'groupdav' || $this->productManufacturer == 'file')
{
foreach($event as $name => $value)
{
if (substr($name, 0, 2) == '##')
{
if (($attr = json_decode($value, true)) && is_array($attr))
{
$vevent->setAttribute(substr($name, 2), $attr['value'], $attr['params'], true, $attr['values']);
}
else
{
$vevent->setAttribute(substr($name, 2), $value);
}
}
}
}
if ($this->productManufacturer == 'nokia')
{
if ($event['special'] == '1')
@ -862,7 +884,7 @@ class calendar_ical extends calendar_boupdate
foreach ((array)$event['alarm'] as $alarmData)
{
// skip over alarms that don't have the minimum required info
if (!$alarmData['offset'] && !$alarmData['time']) continue;
if (!isset($alarmData['offset']) && !isset($alarmData['time'])) continue;
// skip alarms not being set for all users and alarms owned by other users
if ($alarmData['all'] != true && $alarmData['owner'] != $this->user)
@ -899,8 +921,9 @@ class calendar_ical extends calendar_boupdate
// VCalendar 2.0 / RFC 2445
// RFC requires DESCRIPTION for DISPLAY
if (!$event['title'] && !$description) continue;
if (!$event['title'] && !$description) $description = 'Alarm';
/* Disabling for now
// Lightning infinitly pops up alarms for recuring events, if the only use an offset
if ($this->productName == 'lightning' && $event['recur_type'] != MCAL_RECUR_NONE)
{
@ -914,7 +937,7 @@ class calendar_ical extends calendar_boupdate
{
continue;
}
}
}*/
// for SyncML non-whole-day events always use absolute times
// (probably because some devices have no clue about timezones)
@ -930,7 +953,7 @@ class calendar_ical extends calendar_boupdate
if ($alarmData['offset'] !== false)
{
$valarm->setAttribute('TRIGGER', -$alarmData['offset'],
array('VALUE' => 'DURATION', 'RELATED' => 'START'));
array('VALUE' => 'DURATION', 'RELATED' => 'START'));
}
else
{
@ -938,9 +961,28 @@ class calendar_ical extends calendar_boupdate
$value = self::getDateTime($alarmData['time'],$tzid,$params);
$valarm->setAttribute('TRIGGER', $value, $params);
}
$valarm->setAttribute('ACTION','DISPLAY');
$valarm->setAttribute('DESCRIPTION',$event['title'] ? $event['title'] : $description);
if (!empty($alarmData['uid']))
{
$valarm->setAttribute('UID', $alarmData['uid']);
$valarm->setAttribute('X-WR-ALARMUID', $alarmData['uid']);
}
// set evtl. existing attributes set by iCal clients not used by EGroupware
if (isset($alarmData['attrs']))
{
foreach($alarmData['attrs'] as $attr => $data)
{
$valarm->setAttribute($attr, $data['value'], $data['params']);
}
}
// set default ACTION and DESCRIPTION, if not set by a client
if (!isset($alarmData['attrs']) || !isset($alarmData['attrs']['ACTION']))
{
$valarm->setAttribute('ACTION','DISPLAY');
}
if (!isset($alarmData['attrs']) || !isset($alarmData['attrs']['DESCRIPTION']))
{
$valarm->setAttribute('DESCRIPTION',$event['title'] ? $event['title'] : $description);
}
$vevent->addComponent($valarm);
}
}
@ -968,6 +1010,7 @@ class calendar_ical extends calendar_boupdate
{
$paramData['ENCODING'] = 'QUOTED-PRINTABLE';
}
/* disable automatic QP encoding eg. for new-lines as it also encodes non-ascii/utf-8 which TB does NOT decode
else
{
$paramData['CHARSET'] = '';
@ -979,7 +1022,7 @@ class calendar_ical extends calendar_boupdate
{
$paramData['ENCODING'] = '';
}
}
}*/
break;
case 'funambol':
$paramData['ENCODING'] = 'FUNAMBOL-QP';
@ -1216,6 +1259,14 @@ class calendar_ical extends calendar_boupdate
}
}
}
// unset old X-* attributes stored in custom-fields
foreach ($event_info['stored_event'] as $key => $value)
{
if ($key[0] == '#' && $key[1] == '#' && !isset($event[$key]))
{
$event[$key] = '';
}
}
if ($merge)
{
if ($this->log)
@ -1422,46 +1473,9 @@ class calendar_ical extends calendar_boupdate
case 'SERIES-MASTER':
case 'SERIES-EXCEPTION':
case 'SERIES-EXCEPTION-PROPAGATE':
if (count($event['alarm']) > 0)
if (isset($event['alarm']))
{
foreach ($event['alarm'] as $newid => &$alarm)
{
if (!isset($alarm['offset']) && isset($alarm['time']))
{
$alarm['offset'] = $event['start'] - $alarm['time'];
}
elseif (!isset($alarm['time']) && isset($alarm['offset']))
{
$alarm['time'] = $event['start'] - $alarm['offset'];
}
$alarm['owner'] = $this->user;
$alarm['all'] = false;
// if no edit rights, allow participants to set alarms directly (like status)
if ($event_info['stored_event'] && !$event_info['acl_edit'])
{
if ($alarm['time'] < time() && !calendar_so::shift_alarm($event, $alarm))
{
continue; //pgoerzen: don't add an alarm in the past
}
$this->save_alarm($event_info['stored_event']['id'], $alarm);
}
if (is_array($event_info['stored_event'])
&& count($event_info['stored_event']['alarm']) > 0)
{
foreach ($event_info['stored_event']['alarm'] as $alarm_id => $alarm_data)
{
if ($alarm['offset'] == $alarm_data['offset'] &&
($alarm_data['all'] || $alarm_data['owner'] == $this->user))
{
unset($event['alarm'][$newid]);
unset($event_info['stored_event']['alarm'][$alarm_id]);
continue 2;
}
}
}
}
$this->sync_alarms($event, (array)$event_info['stored_event']['alarm'], $this->user);
}
break;
@ -1469,18 +1483,6 @@ class calendar_ical extends calendar_boupdate
// nothing to do here
break;
}
if (is_array($event_info['stored_event'])
&& count($event_info['stored_event']['alarm']) > 0)
{
foreach ($event_info['stored_event']['alarm'] as $alarm_id => $alarm_data)
{
// only touch own alarms
if ($alarm_data['all'] == false && $alarm_data['owner'] == $this->user)
{
$this->delete_alarm($alarm_id);
}
}
}
}
if ($this->log)
@ -1764,6 +1766,73 @@ class calendar_ical extends calendar_boupdate
return $updated_id === 0 ? 0 : $return_id;
}
/**
* Sync alarms of current user: add alarms added on client and remove the ones removed
*
* @param array& $event
* @param array $old_alarms
* @param int $user account_id of user to create alarm for
* @return int number of modified alarms
*/
public function sync_alarms(array &$event, array $old_alarms, $user)
{
if ($this->debug) error_log(__METHOD__."(".array2string($event).', old_alarms='.array2string($old_alarms).", $user,)");
$modified = 0;
foreach($event['alarm'] as &$alarm)
{
// check if alarm is already stored or from other users
foreach($old_alarms as $id => $old_alarm)
{
// not current users alarm --> ignore
if (!$old_alarm['all'] && $old_alarm['owner'] != $user)
{
unset($old_alarm[$id]);
continue;
}
// alarm found --> stop
if (empty($alarm['uid']) && $alarm['offset'] == $old_alarm['offset'] || $alarm['uid'] && $alarm['uid'] == $old_alarm['uid'])
{
unset($old_alarms[$id]);
break;
}
}
// alarm not found --> add it
if ($alarm['offset'] != $old_alarm['offset'] || $old_alarm['owner'] != $user && !$alarm['all'])
{
$alarm['owner'] = $user;
if (!isset($alarm['time'])) $alarm['time'] = $event['start'] - $alarm['offset'];
if ($alarm['time'] < time()) calendar_so::shift_alarm($event, $alarm);
if ($this->debug) error_log(__METHOD__."() adding new alarm from client ".array2string($alarm));
if ($event['id']) $alarm['id'] = $this->save_alarm($event['id'], $alarm);
++$modified;
}
// existing alarm --> update it
elseif ($alarm['offset'] == $old_alarm['offset'] && ($old_alarm['owner'] == $user || $old_alarm['all']))
{
if (!isset($alarm['time'])) $alarm['time'] = $event['start'] - $alarm['offset'];
if ($alarm['time'] < time()) calendar_so::shift_alarm($event, $alarm);
$alarm = array_merge($old_alarm, $alarm);
if ($this->debug) error_log(__METHOD__."() updating existing alarm from client ".array2string($alarm));
$alarm['id'] = $this->save_alarm($event['id'], $alarm);
++$modified;
}
}
// remove all old alarms left from current user
foreach($old_alarms as $id => $old_alarm)
{
// not current users alarm --> ignore
if (!$old_alarm['all'] && $old_alarm['owner'] != $user)
{
unset($old_alarm[$id]);
continue;
}
if ($this->debug) error_log(__METHOD__."() deleting alarm '$id' deleted on client ".array2string($old_alarm));
$this->delete_alarm($id);
++$modified;
}
return $modified;
}
/**
* get the value of an attribute by its name
*
@ -1784,9 +1853,17 @@ class calendar_ical extends calendar_boupdate
return false;
}
static function valarm2egw(&$alarms, &$valarm)
/**
* Parsing a valarm component preserving all attributes unknown to EGw
*
* @param array &$alarms on return alarms parsed
* @param Horde_Icalendar_Valarm $valarm valarm component
* @param int $duration in seconds to be able to convert RELATED=END
* @return int number of parsed alarms
*/
static function valarm2egw(&$alarms, Horde_Icalendar_Valarm $valarm, $duration)
{
$count = 0;
$alarm = array();
foreach ($valarm->getAllAttributes() as $vattr)
{
switch ($vattr['name'])
@ -1797,40 +1874,47 @@ class calendar_ical extends calendar_boupdate
switch ($vtype)
{
case 'DURATION':
if (isset($vattr['params']['RELATED'])
&& $vattr['params']['RELATED'] != 'START')
if (isset($vattr['params']['RELATED']) && $vattr['params']['RELATED'] == 'END')
{
$alarm['offset'] = $duration -$vattr['value'];
}
elseif (isset($vattr['params']['RELATED']) && $vattr['params']['RELATED'] != 'START')
{
error_log("Unsupported VALARM offset anchor ".$vattr['params']['RELATED']);
return;
}
else
{
$alarms[] = array('offset' => -$vattr['value']);
$count++;
$alarm['offset'] = -$vattr['value'];
}
break;
case 'DATE-TIME':
$alarms[] = array('time' => $vattr['value']);
$count++;
$alarm['time'] = $vattr['value'];
break;
default:
// we should also do ;RELATED=START|END
error_log('VALARM/TRIGGER: unsupported value type:' . $vtype);
}
break;
case 'ACTION':
case 'DISPLAY':
case 'DESCRIPTION':
case 'SUMMARY':
case 'ATTACH':
case 'ATTENDEE':
// we ignore these fields silently
break;
default:
error_log('VALARM field ' .$vattr['name'] . ': ' . $vattr['value'] . ' HAS NO CONVERSION YET');
case 'UID':
case 'X-WR-ALARMUID':
$alarm['uid'] = $vattr['value'];
break;
default: // store all other attributes, so we dont loose them
$alarm['attrs'][$vattr['name']] = array(
'params' => $vattr['params'],
'value' => $vattr['value'],
);
}
}
return $count;
if (isset($alarm['offset']) || isset($alarm['time']))
{
//error_log(__METHOD__."(..., ".$valarm->exportvCalendar().", $duration) alarm=".array2string($alarm));
$alarms[] = $alarm;
return 1;
}
return 0;
}
function setSupportedFields($_productManufacturer='', $_productName='')
@ -2246,7 +2330,7 @@ class calendar_ical extends calendar_boupdate
{
if (is_a($valarm, 'Horde_Icalendar_Valarm'))
{
$this->valarm2egw($alarms, $valarm);
self::valarm2egw($alarms, $valarm, $event['end'] - $event['start']);
}
}
$event['alarm'] = $alarms;
@ -2282,19 +2366,6 @@ class calendar_ical extends calendar_boupdate
return false;
}
/*
$mozillaACK = $component->getAttributeDefault('X-MOZ-LASTACK', null);
if ($this->productName == 'lightning' && !isset($mozillaACK))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.'()' .
"X-MOZ-LASTACK found\n",3,$this->logfile);
}
return false;
}
*/
if (!empty($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length']))
{
$minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'];
@ -2908,6 +2979,31 @@ class calendar_ical extends calendar_boupdate
case 'STATUS': // currently no EGroupware event column, but needed as Android uses it to delete single recurrences
$event['status'] = $attributes['value'];
break;
// ignore all PROPS, we dont want to store like X-properties or unsupported props
case 'DTSTAMP':
case 'SEQUENCE':
case 'CREATED':
case 'LAST-MODIFIED':
case 'DTSTART':
case 'DTEND':
case 'DURATION':
case 'X-LIC-ERROR': // parse errors from libical, makes no sense to store them
break;
default: // X- attribute or other by EGroupware unsupported property
//error_log(__METHOD__."() $attributes[name] = ".array2string($attributes));
// for attributes with multiple values in multiple lines, merge the values
if (isset($event['##'.$attributes['name']]))
{
//error_log(__METHOD__."() taskData['##$attribute[name]'] = ".array2string($taskData['##'.$attribute['name']]));
$attributes['values'] = array_merge(
is_array($event['##'.$attributes['name']]) ? $event['##'.$attributes['name']]['values'] : (array)$event['##'.$attributes['name']],
$attributes['values']);
}
$event['##'.$attributes['name']] = $attributes['params'] || count($attributes['values']) > 1 ?
json_encode($attributes) : $attributes['value'];
break;
}
}
// check if the entry is a birthday

View File

@ -1628,8 +1628,10 @@ ORDER BY cal_user_type, cal_usre_id
{
foreach ($event['alarm'] as $id => $alarm)
{
if (is_numeric($id)) unset($alarm['id']); // unset the temporary id to add the alarm
if ($alarm['id'] && strpos($alarm['id'], 'cal:'.$cal_id.':') !== 0)
{
unset($alarm['id']); // unset the temporary id to add the alarm
}
if(!isset($alarm['offset']))
{
$alarm['offset'] = $event['cal_start'] - $alarm['time'];
@ -1784,7 +1786,7 @@ ORDER BY cal_user_type, cal_usre_id
/**
* Combine status, quantity and role into one value
*
* @param string $status
* @param string $status status letter: U, T, A, R
* @param int $quantity =1
* @param string $role ='REQ-PARTICIPANT'
* @return string
@ -2298,6 +2300,8 @@ ORDER BY cal_user_type, cal_usre_id
$this->async->cancel_timer($id);
}
$alarm['cal_id'] = $cal_id; // we need the back-reference
// add an alarm uid, if none is given
if (empty($alarm['uid']) && class_exists('Horde_Support_Uuid')) $alarm['uid'] = (string)new Horde_Support_Uuid;
//error_log(__METHOD__.__LINE__.' Save Alarm for CalID:'.$cal_id.'->'.array2string($alarm).'-->'.$id.'#'.function_backtrace());
// allways store job with the alarm owner as job-owner to get eg. the correct from address
if (!$this->async->set_timer($alarm['time'],$id,'calendar.calendar_boupdate.send_alarm',$alarm,$alarm['owner']))
@ -2855,7 +2859,7 @@ ORDER BY cal_user_type, cal_usre_id
/**
* Moves a datetime to the beginning of the day within timezone
*
* @param egw_time &time the datetime entry
* @param egw_time $time the datetime entry
* @param string tz_id timezone
*
* @return DateTime