mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-02-10 15:30:21 +01:00
Apply Calendar SyncML and GroupDAV changes
This commit is contained in:
parent
3bd935a541
commit
a69887122e
@ -16,6 +16,10 @@ if (!defined('ACL_TYPE_IDENTIFER')) // used to mark ACL-values for the debug_mes
|
||||
define('ACL_TYPE_IDENTIFER','***ACL***');
|
||||
}
|
||||
|
||||
define('HOUR_s',60*60);
|
||||
define('DAY_s',24*HOUR_s);
|
||||
define('WEEK_s',7*DAY_s);
|
||||
|
||||
/**
|
||||
* Gives read access to the calendar, but all events the user is not participating are private!
|
||||
* Used by addressbook.
|
||||
@ -95,6 +99,7 @@ class calendar_bo
|
||||
'R' => 'Rejected',
|
||||
'T' => 'Tentative',
|
||||
'U' => 'No Response',
|
||||
'D' => 'Delegated',
|
||||
'G' => 'Group invitation',
|
||||
);
|
||||
/**
|
||||
@ -291,9 +296,9 @@ class calendar_bo
|
||||
* filter string all (not rejected), accepted, unknown, tentative, rejected or hideprivate
|
||||
* query string pattern so search for, if unset or empty all matching entries are returned (no search)
|
||||
* Please Note: a search never returns repeating events more then once AND does not honor start+end date !!!
|
||||
* dayswise boolean on True it returns an array with YYYYMMDD strings as keys and an array with events
|
||||
* daywise boolean on True it returns an array with YYYYMMDD strings as keys and an array with events
|
||||
* (events spanning multiple days are returned each day again (!)) otherwise it returns one array with
|
||||
* the events (default), not honored in a search ==> always returns an array of events !
|
||||
* the events (default), not honored in a search ==> always returns an array of events!
|
||||
* date_format string date-formats: 'ts'=timestamp (default), 'array'=array, or string with format for date
|
||||
* offset boolean/int false (default) to return all entries or integer offset to return only a limited result
|
||||
* enum_recuring boolean if true or not set (default) or daywise is set, each recurence of a recuring events is returned,
|
||||
@ -463,7 +468,7 @@ class calendar_bo
|
||||
$this->debug_message('socalendar::search daywise sorting from %1 to %2 of %3',False,$start,$end,$events);
|
||||
}
|
||||
// create empty entries for each day in the reported time
|
||||
for($ts = $start; $ts <= $end; $ts += DAY_s)
|
||||
for($ts = $start; $ts <= $end; $ts += DAY_s) // good enough for array creation, but see while loop below.
|
||||
{
|
||||
$daysEvents[$this->date2string($ts)] = array();
|
||||
}
|
||||
@ -474,9 +479,13 @@ class calendar_bo
|
||||
$e_end = min($this->date2ts($event['end'])-1,$end);
|
||||
|
||||
// add event to each day in the reported time
|
||||
for($ts = $e_start; $ts <= $e_end; $ts += DAY_s)
|
||||
$ts = $e_start;
|
||||
// $ts += DAY_s in a 'for' loop does not work for daylight savings in week view
|
||||
// because the day is longer than DAY_s: Fullday events will be added twice.
|
||||
while ($ts <= $e_end)
|
||||
{
|
||||
$daysEvents[$ymd = $this->date2string($ts)][] =& $events[$k];
|
||||
$ts = strtotime("+1 day",$ts);
|
||||
}
|
||||
if ($ymd != ($last = $this->date2string($e_end)))
|
||||
{
|
||||
@ -515,6 +524,59 @@ class calendar_bo
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get integration data for a given app of a part (value for a certain key) of it
|
||||
*
|
||||
* @param string $app
|
||||
* @param string $part
|
||||
* @return array
|
||||
*/
|
||||
static function integration_get_data($app,$part=null)
|
||||
{
|
||||
static $integration_data;
|
||||
|
||||
if (!isset($integration_data))
|
||||
{
|
||||
$integration_data = calendar_so::get_integration_data();
|
||||
}
|
||||
|
||||
if (!isset($integration_data[$app])) return null;
|
||||
|
||||
return $part ? $integration_data[$app][$part] : $integration_data[$app];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get private attribute for an integration event
|
||||
*
|
||||
* Attribute 'is_private' is either a boolean value, eg. false to make all events of $app public
|
||||
* or an ExecMethod callback with parameters $id,$event
|
||||
*
|
||||
* @param string $app
|
||||
* @param int|string $id
|
||||
* @return string
|
||||
*/
|
||||
static function integration_get_private($app,$id,$event)
|
||||
{
|
||||
$app_data = self::integration_get_data($app,'is_private');
|
||||
|
||||
// no method, fall back to link title
|
||||
if (is_null($app_data))
|
||||
{
|
||||
$is_private = !egw_link::title($app,$id);
|
||||
}
|
||||
// boolean value to make all events of $app public (false) or private (true)
|
||||
elseif (is_bool($app_data))
|
||||
{
|
||||
$is_private = $app_data;
|
||||
}
|
||||
else
|
||||
{
|
||||
$is_private = (bool)ExecMethod2($app_data,$id,$event);
|
||||
}
|
||||
//echo '<p>'.__METHOD__."($app,$id,) app_data=".array2string($app_data).' returning '.array2string($is_private)."</p>\n";
|
||||
return $is_private;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all non-private info from a privat event
|
||||
*
|
||||
@ -569,7 +631,7 @@ class calendar_bo
|
||||
$old_horizont = $this->config['horizont'];
|
||||
$this->config['horizont'] = $new_horizont;
|
||||
|
||||
// create further recurrences for all recuring and not yet (at the old horizont) ended events
|
||||
// create further recurrences for all recurring and not yet (at the old horizont) ended events
|
||||
if (($recuring = $this->so->unfinished_recuring($old_horizont)))
|
||||
{
|
||||
foreach($this->read(array_keys($recuring)) as $cal_id => $event)
|
||||
@ -590,7 +652,7 @@ class calendar_bo
|
||||
}
|
||||
|
||||
/**
|
||||
* set all recurrences for an event until defined horizont $this->config['horizont']
|
||||
* set all recurrences for an event until the defined horizont $this->config['horizont']
|
||||
*
|
||||
* This methods operates in usertime, while $this->config['horizont'] is in servertime!
|
||||
*
|
||||
@ -599,22 +661,23 @@ class calendar_bo
|
||||
*/
|
||||
function set_recurrences($event,$start=0)
|
||||
{
|
||||
if ($this->debug && ((int) $this->debug >= 2 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont'))
|
||||
if ($this->debug && ((int) $this->debug >= 2 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont'))
|
||||
{
|
||||
$this->debug_message('bocal::set_recurrences(%1,%2)',true,$event,$start);
|
||||
}
|
||||
// check if the caller gave the event start and end times and if not read them from the DB
|
||||
if (!isset($event['start']) || !isset($event['end']))
|
||||
{
|
||||
$event_read=$this->read($event['id']);
|
||||
$event['start'] = $event_read['start'];
|
||||
$event['end'] = $event_read['end'];
|
||||
}
|
||||
// check if the caller gave the participants and if not read them from the DB
|
||||
if (!isset($event['participants']))
|
||||
// check if the caller gave us enough information and if not read it from the DB
|
||||
if (!isset($event['participants']) || !isset($event['start']) || !isset($event['end']))
|
||||
{
|
||||
list(,$event_read) = each($this->so->read($event['id']));
|
||||
$event['participants'] = $event_read['participants'];
|
||||
if (!isset($event['participants']))
|
||||
{
|
||||
$event['participants'] = $event_read['participants'];
|
||||
}
|
||||
if (!isset($event['start']) || !isset($event['end']))
|
||||
{
|
||||
$event['start'] = $event_read['start'];
|
||||
$event['end'] = $event_read['end'];
|
||||
}
|
||||
}
|
||||
if (!$start) $start = $event['start'];
|
||||
|
||||
@ -644,10 +707,9 @@ class calendar_bo
|
||||
function db2data(&$events,$date_format='ts')
|
||||
{
|
||||
if (!is_array($events)) echo "<p>bocal::db2data(\$events,$date_format) \$events is no array<br />\n".function_backtrace()."</p>\n";
|
||||
foreach($events as $id => &$event)
|
||||
foreach($events as &$event)
|
||||
{
|
||||
// convert timezone id of event to tzid (iCal id like 'Europe/Berlin')
|
||||
unset($event_timezone);
|
||||
if (!$event['tz_id'] || !($event['tzid'] = calendar_timezones::id2tz($event['tz_id'])))
|
||||
{
|
||||
$event['tzid'] = egw_time::$server_timezone->getName();
|
||||
@ -662,7 +724,7 @@ class calendar_bo
|
||||
// same with the recur exceptions
|
||||
if (isset($event['recur_exception']) && is_array($event['recur_exception']))
|
||||
{
|
||||
foreach($event['recur_exception'] as $n => &$date)
|
||||
foreach($event['recur_exception'] as &$date)
|
||||
{
|
||||
$date = $this->date2usertime($date,$date_format);
|
||||
}
|
||||
@ -670,7 +732,7 @@ class calendar_bo
|
||||
// same with the alarms
|
||||
if (isset($event['alarm']) && is_array($event['alarm']))
|
||||
{
|
||||
foreach($event['alarm'] as $n => &$alarm)
|
||||
foreach($event['alarm'] as &$alarm)
|
||||
{
|
||||
$alarm['time'] = $this->date2usertime($alarm['time'],$date_format);
|
||||
}
|
||||
@ -699,7 +761,7 @@ class calendar_bo
|
||||
* @param mixed $date=null date to specify a single event of a series
|
||||
* @param boolean $ignore_acl should we ignore the acl, default False for a single id, true for multiple id's
|
||||
* @param string $date_format='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in servertime, 'array'=array, or string with date-format
|
||||
* @return boolean/array event or array of id => event pairs, false if the acl-check went wrong, null if $ids not found
|
||||
* @return boolean|array event or array of id => event pairs, false if the acl-check went wrong, null if $ids not found
|
||||
*/
|
||||
function read($ids,$date=null,$ignore_acl=False,$date_format='ts')
|
||||
{
|
||||
@ -915,7 +977,6 @@ class calendar_bo
|
||||
$owner = $event['owner'];
|
||||
$private = !$event['public'];
|
||||
}
|
||||
$user = $GLOBALS['egw_info']['user']['account_id'];
|
||||
$grants = $this->grants[$owner];
|
||||
if (is_array($event) && $needed == EGW_ACL_READ)
|
||||
{
|
||||
@ -926,11 +987,11 @@ class calendar_bo
|
||||
{
|
||||
foreach($event['participants'] as $uid => $accept)
|
||||
{
|
||||
if ($uid == $user || $uid < 0 && in_array($user,$GLOBALS['egw']->accounts->members($uid,true)))
|
||||
if ($uid == $this->user || $uid < 0 && in_array($this->user,$GLOBALS['egw']->accounts->members($uid,true)))
|
||||
{
|
||||
// if we are a participant, we have an implicite READ and PRIVAT grant
|
||||
// exept the group gives its members only EGW_ACL_FREEBUSY and the participant is not the current user
|
||||
if ($this->grants[$uid] == EGW_ACL_FREEBUSY && $uid != $user) continue;
|
||||
if ($this->grants[$uid] == EGW_ACL_FREEBUSY && $uid != $this->user) continue;
|
||||
|
||||
$grants |= EGW_ACL_READ | EGW_ACL_PRIVATE;
|
||||
break;
|
||||
@ -958,7 +1019,7 @@ class calendar_bo
|
||||
}
|
||||
else
|
||||
{
|
||||
$access = $user == $owner || $grants & $needed && (!$private || $grants & EGW_ACL_PRIVATE);
|
||||
$access = $this->user == $owner || $grants & $needed && (!$private || $grants & EGW_ACL_PRIVATE);
|
||||
}
|
||||
if ($this->debug && ($this->debug > 2 || $this->debug == 'check_perms'))
|
||||
{
|
||||
@ -974,7 +1035,7 @@ class calendar_bo
|
||||
* string (!) in form YYYYMMDD or iso8601 YYYY-MM-DDThh:mm:ss or YYYYMMDDThhmmss
|
||||
* int already a timestamp
|
||||
* array with keys 'second', 'minute', 'hour', 'day' or 'mday' (depricated !), 'month' and 'year'
|
||||
* @param boolean $user2server_time conversation between user- and server-time default False == Off
|
||||
* @param boolean $user2server=False conversion between user- and server-time; default False == Off
|
||||
*/
|
||||
static function date2ts($date,$user2server=False)
|
||||
{
|
||||
@ -1284,6 +1345,9 @@ class calendar_bo
|
||||
case 'U': // no response = unknown
|
||||
$status = html::image('calendar','cnr-pending',$this->verbose_status[$status]);
|
||||
break;
|
||||
case 'D': // delegated
|
||||
$status = html::image('calendar','forward',$this->verbose_status[$status]);
|
||||
break;
|
||||
case 'G': // group invitation
|
||||
// Todo: Image, seems not to be used
|
||||
$status = '('.$this->verbose_status[$status].')';
|
||||
|
@ -20,6 +20,7 @@ define('MSG_TENTATIVE',4);
|
||||
define('MSG_ACCEPTED',5);
|
||||
define('MSG_ALARM',6);
|
||||
define('MSG_DISINVITE',7);
|
||||
define('MSG_DELEGATED',8);
|
||||
|
||||
/**
|
||||
* Class to access AND manipulate all calendar data (business object)
|
||||
@ -61,9 +62,18 @@ class calendar_boupdate extends calendar_bo
|
||||
var $debug;
|
||||
|
||||
/**
|
||||
* @var string|boolean $log_file filename to enable the login or false for no update-logging
|
||||
* Set Logging
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
var $log_file = false;
|
||||
var $log = false;
|
||||
|
||||
/**
|
||||
* Cached timezone data
|
||||
*
|
||||
* @var array id => data
|
||||
*/
|
||||
protected static $tz_cache = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -135,9 +145,6 @@ class calendar_boupdate extends calendar_bo
|
||||
else
|
||||
{
|
||||
$old_event = $this->read((int)$event['id'],null,$ignore_acl);
|
||||
// if no participants are set, set them from the old event, as we might need them to update recuring events
|
||||
if (!isset($event['participants'])) $event['participants'] = $old_event['participants'];
|
||||
//echo "old $event[id]="; _debug_array($old_event);
|
||||
}
|
||||
|
||||
// do we need to check, if user is allowed to invite the invited participants
|
||||
@ -175,7 +182,7 @@ class calendar_boupdate extends calendar_bo
|
||||
self::has_cat_right(self::CAT_ACL_STATUS,$cat_id,$this->user) === false &&
|
||||
(!$old_event || $old_event['start'] != $event['start'] || $old_event['end'] != $event['end']))
|
||||
{
|
||||
foreach($event['participants'] as $uid => $status)
|
||||
foreach((array)$event['participants'] as $uid => $status)
|
||||
{
|
||||
calendar_so::split_status($status,$q,$r);
|
||||
if ($status != 'U')
|
||||
@ -484,7 +491,7 @@ class calendar_boupdate extends calendar_bo
|
||||
|
||||
// the following switch falls through all cases, as each included the following too
|
||||
//
|
||||
$msg_is_response = $msg_type == MSG_REJECTED || $msg_type == MSG_ACCEPTED || $msg_type == MSG_TENTATIVE;
|
||||
$msg_is_response = $msg_type == MSG_REJECTED || $msg_type == MSG_ACCEPTED || $msg_type == MSG_TENTATIVE || $msg_type == MSG_DELEGATED;
|
||||
|
||||
switch($ru = $part_prefs['calendar']['receive_updates'])
|
||||
{
|
||||
@ -610,6 +617,12 @@ class calendar_boupdate extends calendar_bo
|
||||
$msgtype = '"calendar";';
|
||||
$method = 'REPLY';
|
||||
break;
|
||||
case MSG_DELEGATED:
|
||||
$action = lang('Delegated');
|
||||
$msg = 'Response';
|
||||
$msgtype = '"calendar";';
|
||||
$method = 'REPLY';
|
||||
break;
|
||||
case MSG_ALARM:
|
||||
$action = lang('Alarm');
|
||||
$msg = 'Alarm';
|
||||
@ -642,8 +655,6 @@ class calendar_boupdate extends calendar_bo
|
||||
}
|
||||
}
|
||||
}
|
||||
//$currentPrefs = CreateObject('phpgwapi.preferences',$GLOBALS['egw_info']['user']['account_id']);
|
||||
//$user_prefs = $currentPrefs->read_repository();
|
||||
$user_prefs = $GLOBALS['egw_info']['user']['preferences'];
|
||||
foreach($to_notify as $userid => $statusid)
|
||||
{
|
||||
@ -661,7 +672,8 @@ class calendar_boupdate extends calendar_bo
|
||||
}
|
||||
|
||||
if($userid != $GLOBALS['egw_info']['user']['account_id'] ||
|
||||
($userid == $GLOBALS['egw_info']['user']['account_id'] && $user_prefs['calendar']['receive_own_updates']==1) ||
|
||||
($userid == $GLOBALS['egw_info']['user']['account_id'] &&
|
||||
$user_prefs['calendar']['receive_own_updates']==1) ||
|
||||
$msg_type == MSG_ALARM)
|
||||
{
|
||||
$preferences = CreateObject('phpgwapi.preferences',$userid);
|
||||
@ -1048,7 +1060,14 @@ class calendar_boupdate extends calendar_bo
|
||||
return false;
|
||||
}
|
||||
calendar_so::split_status($status, $quantity, $role);
|
||||
if (($Ok = $this->so->set_status($cal_id,is_numeric($uid)?'u':$uid[0],is_numeric($uid)?$uid:substr($uid,1),$status,$recur_date ? $this->date2ts($recur_date,true) : 0,$role)))
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
"($cal_id, $uid, $status, $recur_date)");
|
||||
}
|
||||
if (($Ok = $this->so->set_status($cal_id,is_numeric($uid)?'u':$uid[0],
|
||||
is_numeric($uid)?$uid:substr($uid,1),$status,
|
||||
$recur_date?$this->date2ts($recur_date,true):0,$role)))
|
||||
{
|
||||
if ($updateTS) $GLOBALS['egw']->contenthistory->updateTimeStamp('calendar',$cal_id,'modify',time());
|
||||
|
||||
@ -1056,6 +1075,7 @@ class calendar_boupdate extends calendar_bo
|
||||
'R' => MSG_REJECTED,
|
||||
'T' => MSG_TENTATIVE,
|
||||
'A' => MSG_ACCEPTED,
|
||||
'D' => MSG_DELEGATED,
|
||||
);
|
||||
if (isset($status2msg[$status]))
|
||||
{
|
||||
@ -1063,10 +1083,42 @@ class calendar_boupdate extends calendar_bo
|
||||
if (isset($recur_date)) $event = $this->read($event['id'],$recur_date); //re-read the actually edited recurring event
|
||||
$this->send_update($status2msg[$status],$event['participants'],$event);
|
||||
}
|
||||
|
||||
}
|
||||
return $Ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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=0)
|
||||
{
|
||||
if (!isset($new_event['participants'])) return;
|
||||
|
||||
// check the old list against the new list
|
||||
foreach ($old_event['participants'] as $userid => $status)
|
||||
{
|
||||
if (!isset($new_event['participants'][$userid])){
|
||||
// Attendee will be deleted this way
|
||||
$new_event['participants'][$userid] = 'G';
|
||||
}
|
||||
elseif ($new_event['participants'][$userid] == $status)
|
||||
{
|
||||
// Same status -- nothing to do.
|
||||
unset($new_event['participants'][$userid]);
|
||||
}
|
||||
}
|
||||
// write the changes
|
||||
foreach ($new_event['participants'] as $userid => $status)
|
||||
{
|
||||
$this->set_status($old_event, $userid, $status, $recur_date, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* deletes an event
|
||||
*
|
||||
@ -1077,8 +1129,6 @@ class calendar_boupdate extends calendar_bo
|
||||
*/
|
||||
function delete($cal_id,$recur_date=0,$ignore_acl=false)
|
||||
{
|
||||
$event = $this->read($cal_id,$recur_date);
|
||||
|
||||
if (!($event = $this->read($cal_id,$recur_date)) ||
|
||||
!$ignore_acl && !$this->check_perms(EGW_ACL_DELETE,$event))
|
||||
{
|
||||
@ -1355,7 +1405,7 @@ class calendar_boupdate extends calendar_bo
|
||||
}
|
||||
|
||||
$cat_id_list = array();
|
||||
foreach ($catname_list as $cat_name)
|
||||
foreach ((array)$catname_list as $cat_name)
|
||||
{
|
||||
$cat_name = trim($cat_name);
|
||||
$cat_id = $this->categories->name2id($cat_name, 'X-');
|
||||
@ -1413,110 +1463,662 @@ class calendar_boupdate extends calendar_bo
|
||||
* Try to find a matching db entry
|
||||
*
|
||||
* @param array $event the vCalendar data we try to find
|
||||
* @param boolean $relax=false if asked to relax, we only match against some key fields
|
||||
* @return the calendar_id of the matching entry or false (if none matches)
|
||||
* @param string filter='exact' exact -> find the matching entry
|
||||
* check -> check (consitency) for identical matches
|
||||
* relax -> be more tolerant
|
||||
* master -> try to find a releated series master
|
||||
* @return array calendar_ids of matching entries
|
||||
*/
|
||||
function find_event($event, $relax=false)
|
||||
function find_event($event, $filter='exact')
|
||||
{
|
||||
$matchingEvents = array();
|
||||
$query = array();
|
||||
if (isset($event['start']))
|
||||
|
||||
if ($this->log)
|
||||
{
|
||||
$query[] = 'cal_start='.$event['start'];
|
||||
}
|
||||
if (isset($event['end']))
|
||||
{
|
||||
$query[] = 'cal_end='.$event['end'];
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
"($filter)[EVENT]:" . array2string($event));
|
||||
}
|
||||
|
||||
foreach (array('title', 'location',
|
||||
'public', 'non_blocking', 'category') as $key)
|
||||
if ($filter == 'master')
|
||||
{
|
||||
if (!empty($event[$key])) $query['cal_'.$key] = $event[$key];
|
||||
$query[] = 'recur_type!='. MCAL_RECUR_NONE;
|
||||
$query['cal_recurrence'] = 0;
|
||||
}
|
||||
|
||||
if ($event['uid'] && ($uidmatch = $this->read($event['uid'])))
|
||||
if (!isset($event['recurrence'])) $event['recurrence'] = 0;
|
||||
|
||||
if ($event['id'])
|
||||
{
|
||||
if ($event['recurrence'])
|
||||
if ($this->log)
|
||||
{
|
||||
// Let's try to find a real exception first
|
||||
$query['cal_uid'] = $event['uid'];
|
||||
$query['cal_recurrence'] = $event['recurrence'];
|
||||
|
||||
if ($foundEvents = parent::search(array(
|
||||
'query' => $query,
|
||||
)))
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'(' . $event['id'] . ")[EventID]");
|
||||
}
|
||||
if (($egwEvent = $this->read($event['id'], 0, false, 'server')))
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
if(is_array($foundEvents))
|
||||
{
|
||||
$event = array_shift($foundEvents);
|
||||
return $event['id'];
|
||||
}
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'()[FOUND]:' . array2string($egwEvent));
|
||||
}
|
||||
// Let's try the "status only" (pseudo) exceptions now
|
||||
if (($egw_event = $this->read($uidmatch['id'], $event['recurrence'])))
|
||||
if ($egwEvent['recur_type'] != MCAL_RECUR_NONE &&
|
||||
(empty($event['uid']) || $event['uid'] == $egwEvent['uid']))
|
||||
{
|
||||
// Do we work with a pseudo exception here?
|
||||
$match = true;
|
||||
foreach (array('start', 'end', 'title', 'priority',
|
||||
'location', 'public', 'non_blocking') as $key)
|
||||
if ($filter == 'master')
|
||||
{
|
||||
if (isset($event[$key])
|
||||
&& $event[$key] != $egw_event[$key])
|
||||
$matchingEvents[] = $egwEvent['id']; // we found the master
|
||||
}
|
||||
if ($event['recur_type'] == $egwEvent['recur_type'])
|
||||
{
|
||||
$matchingEvents[] = $egwEvent['id']; // we found the event
|
||||
}
|
||||
elseif ($event['recur_type'] == MCAL_RECUR_NONE &&
|
||||
$event['recurrence'] != 0)
|
||||
{
|
||||
$exceptions = $this->so->get_recurrence_exceptions($egwEvent, $event['tzid']);
|
||||
if (in_array($event['recurrence'], $exceptions))
|
||||
{
|
||||
$match = false;
|
||||
break;
|
||||
$matchingEvents[] = $egwEvent['id'] . ':' . (int)$event['recurrence'];
|
||||
}
|
||||
}
|
||||
if ($match && is_array($event['participants']))
|
||||
{
|
||||
foreach ($event['participants'] as $attendee => $status)
|
||||
{
|
||||
if (!isset($egw_event['participants'][$attendee])
|
||||
|| $egw_event['participants'][$attendee] != $status)
|
||||
{
|
||||
$match = false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
unset($egw_event['participants'][$attendee]);
|
||||
}
|
||||
}
|
||||
if ($match && !empty($egw_event['participants'])) $match = false;
|
||||
}
|
||||
if ($match) return ($uidmatch['id'] . ':' . $event['recurrence']);
|
||||
|
||||
return false; // We need to create a new pseudo exception
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return $uidmatch['id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($event['id'] && ($found = $this->read($event['id'])))
|
||||
{
|
||||
// We only do a simple consistency check
|
||||
if ($found['title'] == $event['title']
|
||||
&& $found['start'] == $event['start']
|
||||
&& $found['end'] == $event['end'])
|
||||
} elseif ($event['recur_type'] == $egwEvent['recur_type'] &&
|
||||
$filter != 'master' &&
|
||||
strpos($egwEvent['title'], $event['title']) === 0)
|
||||
{
|
||||
return $found['id'];
|
||||
$matchingEvents[] = $egwEvent['id']; // we found the event
|
||||
}
|
||||
}
|
||||
if (!empty($matchingEvents) || $filter == 'exact') return $matchingEvents;
|
||||
}
|
||||
unset($event['id']);
|
||||
|
||||
if($foundEvents = parent::search(array(
|
||||
'query' => $query,
|
||||
)))
|
||||
// only query calendars of users, we have READ-grants from
|
||||
$users = array();
|
||||
foreach(array_keys($this->grants) as $user)
|
||||
{
|
||||
if(is_array($foundEvents))
|
||||
$user = trim($user);
|
||||
if ($this->check_perms(EGW_ACL_READ|EGW_ACL_READ_FOR_PARTICIPANTS|EGW_ACL_FREEBUSY,0,$user))
|
||||
{
|
||||
$event = array_shift($foundEvents);
|
||||
return $event['id'];
|
||||
if ($user && !in_array($user,$users)) // already added?
|
||||
{
|
||||
$users[] = $user;
|
||||
}
|
||||
}
|
||||
elseif ($GLOBALS['egw']->accounts->get_type($user) != 'g')
|
||||
{
|
||||
continue; // for non-groups (eg. users), we stop here if we have no read-rights
|
||||
}
|
||||
// the further code is only for real users
|
||||
if (!is_numeric($user)) continue;
|
||||
|
||||
// for groups we have to include the members
|
||||
if ($GLOBALS['egw']->accounts->get_type($user) == 'g')
|
||||
{
|
||||
$members = $GLOBALS['egw']->accounts->member($user);
|
||||
if (is_array($members))
|
||||
{
|
||||
foreach($members as $member)
|
||||
{
|
||||
// use only members which gave the user a read-grant
|
||||
if (!in_array($member['account_id'],$users) &&
|
||||
$this->check_perms(EGW_ACL_READ|EGW_ACL_FREEBUSY,0,$member['account_id']))
|
||||
{
|
||||
$users[] = $member['account_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // for users we have to include all the memberships, to get the group-events
|
||||
{
|
||||
$memberships = $GLOBALS['egw']->accounts->membership($user);
|
||||
if (is_array($memberships))
|
||||
{
|
||||
foreach($memberships as $group)
|
||||
{
|
||||
if (!in_array($group['account_id'],$users))
|
||||
{
|
||||
$users[] = $group['account_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
if ($filter != 'master' && ($filter != 'exact' || empty($event['uid'])))
|
||||
{
|
||||
if (isset($event['whole_day']) && $event['whole_day'])
|
||||
{
|
||||
if ($filter == 'relax')
|
||||
{
|
||||
$delta = 1800;
|
||||
}
|
||||
else
|
||||
{
|
||||
$delta = 60;
|
||||
}
|
||||
|
||||
// check length with some tolerance
|
||||
$length = $event['end'] - $event['start'] - $delta;
|
||||
$query[] = ('(cal_end-cal_start)>' . $length);
|
||||
$length += 2 * $delta;
|
||||
$query[] = ('(cal_end-cal_start)<' . $length);
|
||||
$query[] = ('cal_start>' . ($event['start'] - 86400));
|
||||
$query[] = ('cal_start<' . ($event['start'] + 86400));
|
||||
}
|
||||
elseif (isset($event['start']))
|
||||
{
|
||||
|
||||
if ($filter == 'relax')
|
||||
{
|
||||
$query[] = ('cal_start>' . ($event['start'] - 3600));
|
||||
$query[] = ('cal_start<' . ($event['start'] + 3600));
|
||||
}
|
||||
else
|
||||
{
|
||||
// we accept a tiny tolerance
|
||||
$query[] = ('cal_start>' . ($event['start'] - 2));
|
||||
$query[] = ('cal_start<' . ($event['start'] + 2));
|
||||
}
|
||||
}
|
||||
$matchFields = array('priority', 'public', 'non_blocking', 'recurrence');
|
||||
foreach ($matchFields as $key)
|
||||
{
|
||||
if (isset($event[$key])) $query['cal_'.$key] = $event[$key];
|
||||
}
|
||||
}
|
||||
if (!empty($event['uid']))
|
||||
{
|
||||
$query['cal_uid'] = $event['uid'];
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'(' . $event['uid'] . ')[EventUID]');
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'[QUERY]: ' . array2string($query));
|
||||
}
|
||||
if (!count($users) || !($foundEvents =
|
||||
$this->so->search(null, null, $users, 0, 'owner', $query)))
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'[NO MATCH]');
|
||||
}
|
||||
return $matchingEvents;
|
||||
}
|
||||
|
||||
$pseudos = array();
|
||||
|
||||
foreach($foundEvents as $egwEvent)
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'[FOUND]: ' . array2string($egwEvent));
|
||||
}
|
||||
if (in_array($egwEvent['id'], $matchingEvents)) continue;
|
||||
|
||||
// convert timezone id of event to tzid (iCal id like 'Europe/Berlin')
|
||||
if (!$egwEvent['tz_id'] || !($egwEvent['tzid'] = calendar_timezones::id2tz($egwEvent['tz_id'])))
|
||||
{
|
||||
$egwEvent['tzid'] = egw_time::$server_timezone->getName();
|
||||
}
|
||||
|
||||
if (in_array($filter, array('exact', 'master')) && !empty($event['uid']))
|
||||
{
|
||||
// UID found
|
||||
if ($egwEvent['recurrence'] == $event['recurrence'])
|
||||
{
|
||||
// We found the exact match
|
||||
$matchingEvents[] = $egwEvent['id'];
|
||||
break;
|
||||
}
|
||||
if (!$egwEvent['recurrence'] && $event['recurrence'])
|
||||
{
|
||||
// We found the master
|
||||
if ($filter == 'master')
|
||||
{
|
||||
$matchingEvents[] = $egwEvent['id'];
|
||||
break;
|
||||
}
|
||||
$exceptions = $this->so->get_recurrence_exceptions($egwEvent, $event['tzid']);
|
||||
if (in_array($event['recurrence'], $exceptions))
|
||||
{
|
||||
// We found a pseudo exception
|
||||
$matchingEvents[] = $egwEvent['id'] . ':' . (int)$event['recurrence'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($event['uid']) && $filter == 'exact') break;
|
||||
|
||||
// check times
|
||||
if ($filter != 'relax')
|
||||
{
|
||||
if (isset($event['whole_day'])&& $event['whole_day'])
|
||||
{
|
||||
if (!$this->so->isWholeDay($egwEvent))
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'() egwEvent is not a whole-day event!');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
elseif ($filter != 'master')
|
||||
{
|
||||
if (abs($event['end'] - $egwEvent['end']) >= 120)
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'() egwEvent length does not match!');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for real match
|
||||
$matchFields = array('title');
|
||||
switch ($filter)
|
||||
{
|
||||
case 'master':
|
||||
break;
|
||||
case 'relax':
|
||||
$matchFields[] = 'location';
|
||||
default:
|
||||
$matchFields[] = 'description';
|
||||
}
|
||||
foreach ($matchFields as $key)
|
||||
{
|
||||
if (!empty($event[$key]) && (empty($egwEvent[$key])
|
||||
|| strpos($egwEvent[$key], $event[$key]) !== 0))
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
"() event[$key] differ: '" . $event[$key] .
|
||||
"' <> '" . $egwEvent[$key]) . "'";
|
||||
}
|
||||
continue 2; // next foundEvent
|
||||
}
|
||||
}
|
||||
|
||||
if ($filter != 'master' && is_array($event['category']))
|
||||
{
|
||||
// check categories
|
||||
$egwCategories = explode(',', $egwEvent['category']);
|
||||
foreach ($egwCategories as $cat_id)
|
||||
{
|
||||
if ($this->categories->check_perms(EGW_ACL_READ, $cat_id) &&
|
||||
!in_array($cat_id, $event['category']))
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
"() egwEvent category $cat_id is missing!");
|
||||
}
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
$newCategories = array_diff($event['category'], $egwCategories);
|
||||
if (!empty($newCategories))
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'() event has additional categories:' . array2string($newCategories));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($filter != 'relax' && $filter != 'master')
|
||||
{
|
||||
// check participants
|
||||
if (is_array($event['participants']))
|
||||
{
|
||||
foreach ($event['participants'] as $attendee => $status)
|
||||
{
|
||||
if (!isset($egwEvent['participants'][$attendee]) &&
|
||||
$attendee != $egwEvent['owner']) // ||
|
||||
//(!$relax && $egw_event['participants'][$attendee] != $status))
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
"() additional event['participants']: $attendee");
|
||||
}
|
||||
continue 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
unset($egwEvent['participants'][$attendee]);
|
||||
}
|
||||
}
|
||||
// ORGANIZER is maybe missing
|
||||
unset($egwEvent['participants'][$egwEvent['owner']]);
|
||||
if (!empty($egwEvent['participants']))
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'() missing event[participants]: ' .
|
||||
array2string($egwEvent['participants']));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($filter != 'master')
|
||||
{
|
||||
if ($event['recur_type'] == MCAL_RECUR_NONE)
|
||||
{
|
||||
if ($egwEvent['recur_type'] != MCAL_RECUR_NONE)
|
||||
{
|
||||
// We found a pseudo Exception
|
||||
$pseudos[] = $egwEvent['id'] . ':' . $event['start'];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
elseif ($filter != 'relax')
|
||||
{
|
||||
// check exceptions
|
||||
// $exceptions[$remote_ts] = $egw_ts
|
||||
$exceptions = $this->so->get_recurrence_exceptions($egwEvent, $event['$tzid'], 0, 0, 'map');
|
||||
if (is_array($event['recur_exception']))
|
||||
{
|
||||
foreach ($event['recur_exception'] as $key => $day)
|
||||
{
|
||||
if (isset($exceptions[$day]))
|
||||
{
|
||||
unset($exceptions[$day]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
"() additional event['recur_exception']: $day");
|
||||
}
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
if (!empty($exceptions))
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'() missing event[recur_exception]: ' .
|
||||
array2string($event['recur_exception']));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// check recurrence information
|
||||
foreach (array('recur_type', 'recur_interval', 'recur_enddate') as $key)
|
||||
{
|
||||
if (isset($event[$key])
|
||||
&& $event[$key] != $egwEvent[$key])
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
"() events[$key] differ: " . $event[$key] .
|
||||
' <> ' . $egwEvent[$key]);
|
||||
}
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$matchingEvents[] = $egwEvent['id']; // exact match
|
||||
if ($filter = 'master') break;
|
||||
}
|
||||
// append pseudos as last entries
|
||||
$matchingEvents = array_merge($matchingEvents, $pseudos);
|
||||
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'[MATCHES]:' . array2string($matchingEvents));
|
||||
}
|
||||
return $matchingEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* classifies an incoming event from the eGW point-of-view
|
||||
*
|
||||
* exceptions: unlike other calendar apps eGW does not create an event exception
|
||||
* if just the participant state changes - therefore we have to distinguish between
|
||||
* real exceptions and status only exceptions
|
||||
*
|
||||
* @param array $event the event to check
|
||||
*
|
||||
* @return array
|
||||
* type =>
|
||||
* SINGLE a single event
|
||||
* SERIES-MASTER the series master
|
||||
* SERIES-EXCEPTION event is a real exception
|
||||
* SERIES-PSEUDO-EXCEPTION event is a status only exception
|
||||
* SERIES-EXCEPTION-PROPAGATE event was a status only exception in the past and is now a real exception
|
||||
* stored_event => if event already exists in the database array with event data or false
|
||||
* master_event => for event type SERIES-EXCEPTION, SERIES-PSEUDO-EXCEPTION or SERIES-EXCEPTION-PROPAGATE
|
||||
* the corresponding series master event array
|
||||
* NOTE: this param is false if event is of type SERIES-MASTER
|
||||
*/
|
||||
function get_event_info($event)
|
||||
{
|
||||
$type = 'SINGLE'; // default
|
||||
$master_event = false; //default
|
||||
$stored_event = false;
|
||||
$recurrence_event = false;
|
||||
$wasPseudo = false;
|
||||
|
||||
if (($foundEvents = $this->find_event($event, 'exact')))
|
||||
{
|
||||
// We found the exact match
|
||||
$eventID = array_shift($foundEvents);
|
||||
if (strstr($eventID, ':'))
|
||||
{
|
||||
$type = 'SERIES-PSEUDO-EXCEPTION';
|
||||
$wasPseudo = true;
|
||||
list($eventID, $recur_date) = explode(':', $eventID);
|
||||
$recur_date = $this->date2usertime($recur_date);
|
||||
$stored_event = $this->read($eventID, $recur_date, false, 'server');
|
||||
$master_event = $this->read($eventID, 0, false, 'server');
|
||||
$recurrence_event = $stored_event;
|
||||
}
|
||||
else
|
||||
{
|
||||
$stored_event = $this->read($eventID, 0, false, 'server');
|
||||
}
|
||||
if (!empty($stored_event['uid']) && empty($event['uid']))
|
||||
{
|
||||
$event['uid'] = $stored_event['uid']; // restore the UID if it was not delivered
|
||||
}
|
||||
}
|
||||
|
||||
if ($event['recur_type'] != MCAL_RECUR_NONE)
|
||||
{
|
||||
$type = 'SERIES-MASTER';
|
||||
}
|
||||
|
||||
if ($type == 'SINGLE' &&
|
||||
($foundEvents = $this->find_event($event, 'master')))
|
||||
{
|
||||
// SINGLE, SERIES-EXCEPTION OR SERIES-EXCEPTON-STATUS
|
||||
foreach ($foundEvents as $eventID)
|
||||
{
|
||||
// Let's try to find a related series
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
"()[MASTER]: $eventID");
|
||||
}
|
||||
if (($master_event = $this->read($eventID, 0, false, 'server')))
|
||||
{
|
||||
if (isset($stored_event['id']) && $master_event['id'] != $stored_event['id'])
|
||||
{
|
||||
$type = 'SERIES-EXCEPTION'; // this is an existing exception
|
||||
break;
|
||||
}
|
||||
elseif (isset($event['recurrence']) &&
|
||||
in_array($event['recurrence'], $master_event['recur_exception']))
|
||||
{
|
||||
$type = 'SERIES-PSEUDO-EXCEPTION'; // could also be a real one
|
||||
$recurrence_event = $master_event;
|
||||
$recurrence_event['start'] = $event['recurrence'];
|
||||
$recurrence_event['end'] -= $master_event['start'] - $event['recurrence'];
|
||||
break;
|
||||
}
|
||||
elseif (in_array($event['start'], $master_event['recur_exception']))
|
||||
{
|
||||
$type='SERIES-PSEUDO-EXCEPTION'; // new pseudo exception?
|
||||
$recurrence_event = $master_event;
|
||||
$recurrence_event['start'] = $event['start'];
|
||||
$recurrence_event['end'] -= $master_event['start'] - $event['start'];
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// try to find a suitable pseudo exception date
|
||||
$egw_rrule = calendar_rrule::event2rrule($master_event, false);
|
||||
$egw_rrule->current = clone $egw_rrule->time;
|
||||
while ($egw_rrule->valid())
|
||||
{
|
||||
$occurrence = egw_time::to($egw_rrule->current(), 'server');
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'() try occurrence ' . $egw_rrule->current() . " ($occurrence)");
|
||||
}
|
||||
if ($event['start'] == $occurrence)
|
||||
{
|
||||
$type = 'SERIES-PSEUDO-EXCEPTION'; // let's try a pseudo exception
|
||||
$recurrence_event = $master_event;
|
||||
$recurrence_event['start'] = $occurrence;
|
||||
$recurrence_event['end'] -= $master_event['start'] - $occurrence;
|
||||
break 2;
|
||||
}
|
||||
if (isset($event['recurrence']) && $event['recurrence'] == $occurrence)
|
||||
{
|
||||
$type = 'SERIES-EXCEPTION-PROPAGATE';
|
||||
if ($stored_event)
|
||||
{
|
||||
unset($stored_event['id']); // signal the true exception
|
||||
$stored_event['recur_type'] = MCAL_RECUR_NONE;
|
||||
}
|
||||
break 2;
|
||||
}
|
||||
$egw_rrule->next_no_exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check pseudo exception propagation
|
||||
if ($recurrence_event)
|
||||
{
|
||||
// default if we cannot find a proof for a fundamental change
|
||||
// the recurrence_event is the master event with start and end adjusted to the recurrence
|
||||
// check for changed data
|
||||
foreach (array('start','end','uid','title','location','description',
|
||||
'priority','public','special','non_blocking') as $key)
|
||||
{
|
||||
if (!empty($event[$key]) && $recurrence_event[$key] != $event[$key])
|
||||
{
|
||||
if ($wasPseudo)
|
||||
{
|
||||
// We started with a pseudo exception
|
||||
$type = 'SERIES-EXCEPTION-PROPAGATE';
|
||||
}
|
||||
else
|
||||
{
|
||||
$type = 'SERIES-EXCEPTION';
|
||||
}
|
||||
|
||||
if ($stored_event)
|
||||
{
|
||||
unset($stored_event['id']); // signal the true exception
|
||||
$stored_event['recur_type'] = MCAL_RECUR_NONE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// the event id here is always the id of the master event
|
||||
// unset it to prevent confusion of stored event and master event
|
||||
unset($event['id']);
|
||||
}
|
||||
|
||||
// check ACL
|
||||
if (is_array($master_event))
|
||||
{
|
||||
$acl_edit = $this->check_perms(EGW_ACL_EDIT, $master_event['id']);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (is_array($stored_event))
|
||||
{
|
||||
$acl_edit = $this->check_perms(EGW_ACL_EDIT, $stored_event['id']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$acl_edit = true; // new event
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'type' => $type,
|
||||
'acl_edit' => $acl_edit,
|
||||
'stored_event' => $stored_event,
|
||||
'master_event' => $master_event,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all timestamps for a given event from server-time to user-time.
|
||||
* The update() and save() methods expect timestamps in user-time.
|
||||
* @param &$event the event we are working on
|
||||
*
|
||||
*/
|
||||
function server2usertime (&$event)
|
||||
{
|
||||
// we run all dates through date2usertime, to adjust to user-time
|
||||
foreach(array('start','end','recur_enddate','recurrence') as $ts)
|
||||
{
|
||||
// we convert here from server-time to timestamps in user-time!
|
||||
if (isset($event[$ts])) $event[$ts] = $event[$ts] ? $this->date2usertime($event[$ts]) : 0;
|
||||
}
|
||||
// same with the recur exceptions
|
||||
if (isset($event['recur_exception']) && is_array($event['recur_exception']))
|
||||
{
|
||||
foreach($event['recur_exception'] as $n => $date)
|
||||
{
|
||||
$event['recur_exception'][$n] = $this->date2usertime($date);
|
||||
}
|
||||
}
|
||||
// same with the alarms
|
||||
if (isset($event['alarm']) && is_array($event['alarm']))
|
||||
{
|
||||
foreach($event['alarm'] as $id => $alarm)
|
||||
{
|
||||
$event['alarm'][$id]['time'] = $this->date2usertime($alarm['time']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -58,10 +58,11 @@ class calendar_groupdav extends groupdav_handler
|
||||
* @param string $app 'calendar', 'addressbook' or 'infolog'
|
||||
* @param int $debug=null debug-level to set
|
||||
* @param string $base_uri=null base url of handler
|
||||
* @param string $principalURL=null principal url of handler
|
||||
*/
|
||||
function __construct($app,$debug=null, $base_uri=null)
|
||||
function __construct($app,$debug=null, $base_uri=null, $principalURL=null)
|
||||
{
|
||||
parent::__construct($app,$debug,$base_uri);
|
||||
parent::__construct($app,$debug,$base_uri,$principalURL);
|
||||
|
||||
$this->bo = new calendar_boupdate();
|
||||
}
|
||||
@ -83,7 +84,7 @@ class calendar_groupdav extends groupdav_handler
|
||||
if (!is_array($event)) $event = $this->bo->read($event);
|
||||
$name = $event[self::PATH_ATTRIBUTE];
|
||||
}
|
||||
return '/calendar/'.$name.'.ics';
|
||||
return $name.'.ics';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,8 +99,11 @@ class calendar_groupdav extends groupdav_handler
|
||||
*/
|
||||
function propfind($path,$options,&$files,$user,$id='')
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."($path,".array2string($options).",,$user,$id)");
|
||||
$starttime = microtime(true);
|
||||
if ($this->debug)
|
||||
{
|
||||
error_log(__METHOD__."($path,".array2string($options).",,$user,$id)");
|
||||
$starttime = microtime(true);
|
||||
}
|
||||
|
||||
// ToDo: add parameter to only return id & etag
|
||||
$cal_filters = array(
|
||||
@ -110,16 +114,22 @@ class calendar_groupdav extends groupdav_handler
|
||||
'daywise' => false,
|
||||
'date_format' => 'server',
|
||||
);
|
||||
/*
|
||||
if ($this->client_shared_uid_exceptions)
|
||||
{
|
||||
$cal_filters['query']['cal_reference'] = 0;
|
||||
}
|
||||
*/
|
||||
// process REPORT filters or multiget href's
|
||||
if (($id || $options['root']['name'] != 'propfind') && !$this->_report_filters($options,$cal_filters,$id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ($this->debug > 1) error_log(__METHOD__."($path,,,$user,$id) cal_filters=".array2string($cal_filters));
|
||||
if ($this->debug > 1)
|
||||
{
|
||||
error_log(__METHOD__."($path,,,$user,$id) cal_filters=".
|
||||
array2string($cal_filters));
|
||||
}
|
||||
|
||||
// check if we have to return the full calendar data or just the etag's
|
||||
if (!($calendar_data = $options['props'] == 'all' && $options['root']['ns'] == groupdav::CALDAV) && is_array($options['props']))
|
||||
@ -137,8 +147,17 @@ class calendar_groupdav extends groupdav_handler
|
||||
if ($events)
|
||||
{
|
||||
// get all max user modified times at once
|
||||
foreach($events as &$event)
|
||||
foreach($events as $k => &$event)
|
||||
{
|
||||
if ($this->client_shared_uid_exceptions &&
|
||||
$event['reference'] &&
|
||||
($master = $this->bo->read($event['reference'], 0, false, 'server')) &&
|
||||
array_search($event['recurrence'], $master['recur_exception']) !== false)
|
||||
{
|
||||
// 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);
|
||||
@ -153,7 +172,7 @@ class calendar_groupdav extends groupdav_handler
|
||||
'text/calendar; charset=utf-8; component=VEVENT' : 'text/calendar'),
|
||||
// getlastmodified and getcontentlength are required by WebDAV and Cadaver eg. reports 404 Not found if not set
|
||||
HTTP_WebDAV_Server::mkprop('getlastmodified', $event['modified']),
|
||||
HTTP_WebDAV_Server::mkprop('resourcetype',''), // iPhone requires that attribute!
|
||||
HTTP_WebDAV_Server::mkprop('resourcetype',''), // DAVKit requires that attribute!
|
||||
);
|
||||
//error_log(__FILE__ . __METHOD__ . "Calendar Data : $calendar_data");
|
||||
if ($calendar_data)
|
||||
@ -167,12 +186,16 @@ class calendar_groupdav extends groupdav_handler
|
||||
$props[] = HTTP_WebDAV_Server::mkprop('getcontentlength', ''); // expensive to calculate and no CalDAV client uses it
|
||||
}
|
||||
$files['files'][] = array(
|
||||
'path' => self::get_path($event),
|
||||
'path' => $path.self::get_path($event),
|
||||
'props' => $props,
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($this->debug) error_log(__METHOD__."($path) took ".(microtime(true) - $starttime).' to return '.count($files['files']).' items');
|
||||
if ($this->debug)
|
||||
{
|
||||
error_log(__METHOD__."($path) took ".(microtime(true) - $starttime).
|
||||
' to return '.count($files['files']).' items');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -206,7 +229,7 @@ class calendar_groupdav extends groupdav_handler
|
||||
return false; // return nothing for now, todo: check if we can pass it on to the infolog handler
|
||||
// todos are handled by the infolog handler
|
||||
//$infolog_handler = new groupdav_infolog();
|
||||
//return $infolog_handler->propfind($path,$options,$files,$user,$method);
|
||||
//return $infolog_handler->propfind($options['path'],$options,$options['files'],$user,$method);
|
||||
case 'VCALENDAR':
|
||||
case 'VEVENT':
|
||||
break; // that's our default anyway
|
||||
@ -273,11 +296,9 @@ class calendar_groupdav extends groupdav_handler
|
||||
{
|
||||
foreach($options['other'] as $option)
|
||||
{
|
||||
|
||||
if ($option['name'] == 'href')
|
||||
{
|
||||
$parts = explode('/',$option['data']);
|
||||
|
||||
if (is_numeric($id = basename(array_pop($parts),'.ics'))) $ids[] = $id;
|
||||
}
|
||||
}
|
||||
@ -350,22 +371,28 @@ class calendar_groupdav extends groupdav_handler
|
||||
*/
|
||||
private static function &get_series($uid,calendar_bo $bo=null)
|
||||
{
|
||||
if (is_null($bo)) $bo = new calendar_bo();
|
||||
if (is_null($bo)) $bo = new calendar_bopdate();
|
||||
|
||||
if (!($masterId = array_shift($bo->find_event(array('uid' => $uid), 'master')))
|
||||
|| !($master = $bo->read($masterId, 0, false, 'server')))
|
||||
{
|
||||
return array(); // should never happen
|
||||
}
|
||||
|
||||
$exceptions = $master['recur_exception'];
|
||||
|
||||
$events =& $bo->search(array(
|
||||
'query' => array('cal_uid' => $uid),
|
||||
'daywise' => false,
|
||||
'date_format' => 'server',
|
||||
));
|
||||
$master = null;
|
||||
$events = array_merge(array($master), $events);
|
||||
foreach($events as $k => &$recurrence)
|
||||
{
|
||||
if (!isset($master)) // first event is allways the series master
|
||||
{
|
||||
$master =& $events[$k];
|
||||
//error_log('master: '.array2string($master));
|
||||
continue; // nothing to change
|
||||
}
|
||||
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
// "($uid)[$k]:" . array2string($recurrence));
|
||||
if (!$k) continue; // nothing to change
|
||||
|
||||
if ($recurrence['id'] != $master['id']) // real exception
|
||||
{
|
||||
//error_log('real exception: '.array2string($recurrence));
|
||||
@ -373,9 +400,9 @@ class calendar_groupdav extends groupdav_handler
|
||||
// at least Lightning "understands" EXDATE as exception from what's included
|
||||
// in the whole resource / VCALENDAR component
|
||||
// not removing it causes Lightning to remove the exception itself
|
||||
if (($k = array_search($recurrence['recurrence'],$master['recur_exception'])) !== false)
|
||||
if (($e = array_search($recurrence['recurrence'],$exceptions)) !== false)
|
||||
{
|
||||
unset($master['recur_exception'][$k]);
|
||||
unset($exceptions[$e]);
|
||||
}
|
||||
continue; // nothing to change
|
||||
}
|
||||
@ -386,13 +413,14 @@ class calendar_groupdav extends groupdav_handler
|
||||
unset($events[$k]); // no exception --> remove it
|
||||
continue;
|
||||
}
|
||||
// this is a virtual excetion now (no extra event/cal_id in DB)
|
||||
// this is a virtual exception now (no extra event/cal_id in DB)
|
||||
//error_log('virtual exception: '.array2string($recurrence));
|
||||
$recurrence['recurrence'] = $recurrence['start'];
|
||||
$recurrence['reference'] = $master['id'];
|
||||
$recurrence['recur_type'] = MCAL_RECUR_NONE; // is set, as this is a copy of the master
|
||||
// not for included exceptions (Lightning): $master['recur_exception'][] = $recurrence['start'];
|
||||
}
|
||||
$events[0]['recur_exception'] = $exceptions;
|
||||
return $events;
|
||||
}
|
||||
|
||||
@ -406,29 +434,79 @@ class calendar_groupdav extends groupdav_handler
|
||||
*/
|
||||
function put(&$options,$id,$user=null)
|
||||
{
|
||||
if($this->debug) error_log(__METHOD__."($id, $user)".print_r($options,true));
|
||||
$return_no_access=true; // as handled by importVCal anyway and allows it to set the status for participants
|
||||
$event = $this->_common_get_put_delete('PUT',$options,$id,$return_no_access);
|
||||
if ($this->debug) error_log(__METHOD__."($id, $user)".print_r($options,true));
|
||||
|
||||
if (!is_null($event) && !is_array($event))
|
||||
$return_no_access=true; // as handled by importVCal anyway and allows it to set the status for participants
|
||||
$oldEvent = $this->_common_get_put_delete('PUT',$options,$id,$return_no_access);
|
||||
if (!is_null($oldEvent) && !is_array($oldEvent))
|
||||
{
|
||||
if($this->debug) error_log(__METHOD__.print_r($event,true).function_backtrace());
|
||||
return $event;
|
||||
if ($this->debug) error_log(__METHOD__.print_r($oldEvent,true).function_backtrace());
|
||||
return $oldEvent;
|
||||
}
|
||||
|
||||
if (is_null($oldEvent) && !$this->bo->check_perms(EGW_ACL_ADD, 0, $user))
|
||||
{
|
||||
// we have no add permission on this user's calendar
|
||||
if ($this->debug) error_log(__METHOD__."(,$user) we have not enough rights on this calendar");
|
||||
return '403 Forbidden';
|
||||
}
|
||||
|
||||
$handler = $this->_get_handler();
|
||||
if (!($cal_id = $handler->importVCal($options['content'],is_numeric($id) ? $id : -1,
|
||||
self::etag2value($this->http_if_match))))
|
||||
$vCalendar = htmlspecialchars_decode($options['content']);
|
||||
|
||||
if (is_array($oldEvent))
|
||||
{
|
||||
$eventId = $oldEvent['id'];
|
||||
if ($return_no_access)
|
||||
{
|
||||
$retval = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// let lightning think the event is added
|
||||
$retval = '201 Created';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// new entry?
|
||||
if (($foundEvents = $handler->search($vCalendar)))
|
||||
{
|
||||
if (($eventId = array_shift($foundEvents)) &&
|
||||
(list($eventId) = explode(':', $eventId)) &&
|
||||
($oldEvent = $this->bo->read($eventId)))
|
||||
{
|
||||
$retval = '301 Moved Permanently';
|
||||
}
|
||||
else
|
||||
{
|
||||
// to be safe
|
||||
$eventId = -1;
|
||||
$retval = '201 Created';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// new entry
|
||||
$eventId = -1;
|
||||
$retval = '201 Created';
|
||||
}
|
||||
}
|
||||
|
||||
if (!($cal_id = $handler->importVCal($vCalendar, $eventId,
|
||||
self::etag2value($this->http_if_match), false, 0, $this->principalURL, $user)))
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."(,$id) importVCal($options[content]) returned false");
|
||||
return '403 Forbidden';
|
||||
}
|
||||
|
||||
header('ETag: '.$this->get_etag($cal_id));
|
||||
if (is_null($event) || !$return_no_access) // let lightning think the event is added
|
||||
if ($retval !== true)
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."(,$id,$user) cal_id=$cal_id, is_null(\$event)=".(int)is_null($event));
|
||||
header('Location: '.$this->base_uri.self::get_path($cal_id));
|
||||
return '201 Created';
|
||||
$path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']);
|
||||
if ($this->debug) error_log(__METHOD__."(,$id,$user) cal_id=$cal_id: $retval");
|
||||
header('Location: '.$this->base_uri.$path.self::get_path($cal_id));
|
||||
return $retval;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -443,15 +521,16 @@ class calendar_groupdav extends groupdav_handler
|
||||
*/
|
||||
static function fix_series(array &$events)
|
||||
{
|
||||
foreach($events as $n => $event) error_log(__METHOD__." $n before: ".array2string($event));
|
||||
$master =& $events[0];
|
||||
//foreach($events as $n => $event) error_log(__METHOD__." $n before: ".array2string($event));
|
||||
//$master =& $events[0];
|
||||
|
||||
$bo = new calendar_boupdate();
|
||||
|
||||
// get array with orginal recurrences indexed by recurrence-id
|
||||
$org_recurrences = array();
|
||||
foreach(self::get_series($master['uid'],$bo) as $event)
|
||||
$org_recurrences = $exceptions = array();
|
||||
foreach(self::get_series($events[0]['uid'],$bo) as $k => $event)
|
||||
{
|
||||
if (!$k) $master = $event;
|
||||
if ($event['recurrence'])
|
||||
{
|
||||
$org_recurrences[$event['recurrence']] = $event;
|
||||
@ -459,43 +538,50 @@ class calendar_groupdav extends groupdav_handler
|
||||
}
|
||||
|
||||
// assign cal_id's to already existing recurrences and evtl. re-add recur_exception to master
|
||||
foreach($events as &$recurrence)
|
||||
foreach($events as $k => &$recurrence)
|
||||
{
|
||||
if ($recurrence['id'] || !$recurrence['recurrence']) continue; // master
|
||||
if (!$recurrence['recurrence'])
|
||||
{
|
||||
// master
|
||||
$recurrence['id'] = $master['id'];
|
||||
$master =& $events[$k];
|
||||
continue;
|
||||
}
|
||||
|
||||
// from now on we deal with exceptions
|
||||
$org_recurrence = $org_recurrences[$recurrence['recurrence']];
|
||||
if (isset($org_recurrence)) // already existing recurrence
|
||||
{
|
||||
error_log(__METHOD__.'() setting id #'.$org_recurrence['id']).' for '.$recurrence['recurrence'].' = '.date('Y-m-d H:i:s',$recurrence['recurrence']);
|
||||
//error_log(__METHOD__.'() setting id #'.$org_recurrence['id']).' for '.$recurrence['recurrence'].' = '.date('Y-m-d H:i:s',$recurrence['recurrence']);
|
||||
$recurrence['id'] = $org_recurrence['id'];
|
||||
|
||||
// re-add (non-virtual) exceptions to master's recur_exception
|
||||
if ($recurrence['id'] != $master['id'])
|
||||
{
|
||||
error_log(__METHOD__.'() re-adding recur_exception '.$recurrence['recurrence'].' = '.date('Y-m-d H:i:s',$recurrence['recurrence']));
|
||||
$master['recur_exception'][] = $recurrence['recurrence'];
|
||||
//error_log(__METHOD__.'() re-adding recur_exception '.$recurrence['recurrence'].' = '.date('Y-m-d H:i:s',$recurrence['recurrence']));
|
||||
$exceptions[] = $recurrence['recurrence'];
|
||||
}
|
||||
// remove recurrence to be able to detect deleted exceptions
|
||||
unset($org_recurrences[$recurrence['recurrence']]);
|
||||
}
|
||||
}
|
||||
$master['recur_exception'] = array_merge($exceptions, $master['recur_exception']);
|
||||
|
||||
// delete not longer existing recurrences
|
||||
foreach($org_recurrences as $org_recurrence)
|
||||
{
|
||||
if ($org_recurrence['id'] != $master['id']) // non-virtual recurrence
|
||||
{
|
||||
error_log(__METHOD__.'() deleting #'.$org_recurrence['id']);
|
||||
//error_log(__METHOD__.'() deleting #'.$org_recurrence['id']);
|
||||
$bo->delete($org_recurrence['id']); // might fail because of permissions
|
||||
}
|
||||
else // virtual recurrence
|
||||
{
|
||||
error_log(__METHOD__.'() ToDO: delete virtual exception '.$org_recurrence['recurrence'].' = '.date('Y-m-d H:i:s',$org_recurrence['recurrence']));
|
||||
// todo: reset status and participants to master default
|
||||
//error_log(__METHOD__.'() ToDO: delete virtual exception '.$org_recurrence['recurrence'].' = '.date('Y-m-d H:i:s',$org_recurrence['recurrence']));
|
||||
$bo->update_status($master, $org_recurrence, $org_recurrence['recurrence']);
|
||||
}
|
||||
}
|
||||
foreach($events as $n => $event) error_log(__METHOD__." $n after: ".array2string($event));
|
||||
//foreach($events as $n => $event) error_log(__METHOD__." $n after: ".array2string($event));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -533,8 +619,7 @@ class calendar_groupdav extends groupdav_handler
|
||||
*/
|
||||
function read($id)
|
||||
{
|
||||
//$cal_read = $this->bo->read($id,null,false,'server');//njv: do we actually get anything
|
||||
if ($this->debug > 1) error_log("bo-ical read :$id:");//njv:
|
||||
if ($this->debug > 1) error_log("bo-ical read :$id:");
|
||||
return $this->bo->read($id,null,false,'server');
|
||||
}
|
||||
|
||||
@ -575,12 +660,12 @@ class calendar_groupdav extends groupdav_handler
|
||||
{
|
||||
if ($recurrence['reference']) // ignore series master
|
||||
{
|
||||
$etag .= ':'.substr($this->get_etag($recurrence),1,-1);
|
||||
$etag .= ':'.substr($this->get_etag($recurrence),4,-4);
|
||||
}
|
||||
}
|
||||
}
|
||||
//error_log(__METHOD__ . "($entry[id] ($entry[etag]): $entry[title] --> etag=$etag");
|
||||
return '"'.$etag.'"';
|
||||
return 'EGw-'.$etag.'-wGE';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -596,23 +681,74 @@ class calendar_groupdav extends groupdav_handler
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra properties for calendar collections
|
||||
* Add the privileges of the current user
|
||||
*
|
||||
* @param array $props=array() regular props by the groupdav handler
|
||||
* @return array
|
||||
*/
|
||||
static function extra_properties(array $props=array())
|
||||
static function current_user_privilege_set(array $props=array())
|
||||
{
|
||||
// calendaring URL of the current user
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',$_SERVER['SCRIPT_NAME'].'/');
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::DAV,'current-user-privilege-set',
|
||||
array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'privilege',
|
||||
array(//HTTP_WebDAV_Server::mkprop(groupdav::DAV,'all',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'read',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'read-free-busy',''),
|
||||
//HTTP_WebDAV_Server::mkprop(groupdav::DAV,'read-current-user-privilege-set',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'bind',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'unbind',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-post',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-post-vevent',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-respond',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-respond-vevent',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-deliver',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-deliver-vevent',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write-properties',''),
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write-content',''),
|
||||
))));
|
||||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra properties for calendar collections
|
||||
*
|
||||
* @param array $props=array() regular props by the groupdav handler
|
||||
* @param string $displayname
|
||||
* @param string $base_uri=null base url of handler
|
||||
* @return array
|
||||
*/
|
||||
static function extra_properties(array $props=array(), $displayname, $base_uri=null)
|
||||
{
|
||||
// calendar description
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-description',$displayname);
|
||||
// BOX URLs of the current user
|
||||
/*
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'schedule-inbox-URL',
|
||||
array(HTTP_WebDAV_Server::mkprop(self::DAV,'href',$base_uri.'/calendar/')));
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'schedule-outbox-URL',
|
||||
array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'href',$base_uri.'/calendar/')));
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'schedule-default-calendar-URL',
|
||||
array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'href',$base_uri.'/calendar/')));
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALENDARSERVER,'dropbox-home-URL',
|
||||
array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'href',$base_uri.'/calendar/')));
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALENDARSERVER,'notifications-URL',
|
||||
array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'href',$base_uri.'/calendar/')));
|
||||
*/
|
||||
// email of the current user, see caldav-sheduling draft
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set','MAILTO:'.$GLOBALS['egw_info']['user']['email']);
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set',array(
|
||||
HTTP_WebDAV_Server::mkprop('href','MAILTO:'.$GLOBALS['egw_info']['user']['email'])));
|
||||
// supported components, currently only VEVENT
|
||||
$props[] = $sc = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'supported-calendar-component-set',array(
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'supported-calendar-component-set',array(
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VEVENT')),
|
||||
// HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VTODO')), // not yet supported
|
||||
// HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VTODO')), // not yet supported
|
||||
));
|
||||
|
||||
$props[] = HTTP_WebDAV_Server::mkprop('supported-report-set',array(
|
||||
HTTP_WebDAV_Server::mkprop('supported-report',array(
|
||||
HTTP_WebDAV_Server::mkprop('report',
|
||||
HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-multiget'))))));
|
||||
|
||||
//$props = self::current_user_privilege_set($props);
|
||||
return $props;
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@
|
||||
* @link http://www.egroupware.org
|
||||
* @package calendar
|
||||
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @author Joerg Lehrke <jlehrke@noc.de>
|
||||
* @copyright (c) 2009 by RalfBecker-At-outdoor-training.de
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @version $Id$
|
||||
@ -19,7 +20,7 @@
|
||||
* There's a static factory method calendar_rrule::event2rrule(array $event,$usertime=true), which converts an
|
||||
* event read by calendar_bo::read() or calendar_bo::search() to a rrule iterator.
|
||||
*
|
||||
* The rrule iterator object can be casted to string, to get a human readable describtion of the rrule.
|
||||
* The rrule iterator object can be casted to string, to get a human readable description of the rrule.
|
||||
*
|
||||
* There's an interactive test-form, if the class get's called directly: http://localhost/egroupware/calendar/inc/class.calendar_rrule.inc.php
|
||||
*
|
||||
@ -192,6 +193,12 @@ class calendar_rrule implements Iterator
|
||||
*/
|
||||
protected $lastdayofweek;
|
||||
|
||||
/**
|
||||
* Cached timezone data
|
||||
*
|
||||
* @var array id => data
|
||||
*/
|
||||
protected static $tz_cache = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -426,6 +433,12 @@ class calendar_rrule implements Iterator
|
||||
public function rewind()
|
||||
{
|
||||
$this->current = clone $this->time;
|
||||
while ($this->valid() &&
|
||||
$this->exceptions &&
|
||||
in_array($this->current->format('Ymd'),$this->exceptions))
|
||||
{
|
||||
$this->next_no_exception();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -517,18 +530,21 @@ class calendar_rrule implements Iterator
|
||||
/**
|
||||
* Generate a VEVENT RRULE
|
||||
* @param string $version='1.0' could be '2.0', too
|
||||
*
|
||||
* $return array vCalendar RRULE
|
||||
*/
|
||||
public function generate_rrule($version='1.0')
|
||||
{
|
||||
static $utc;
|
||||
$repeat_days = array();
|
||||
$rrule = array();
|
||||
|
||||
if (is_null($utc))
|
||||
if (!isset(self::$tz_cache['UTC']))
|
||||
{
|
||||
$utc = calendar_timezones::DateTimeZone('UTC');
|
||||
self::$tz_cache['UTC'] = calendar_timezones::DateTimeZone('UTC');
|
||||
}
|
||||
|
||||
$utc = self::$tz_cache['UTC'];
|
||||
|
||||
if ($this->type == self::NONE) return false; // no recuring event
|
||||
|
||||
if ($version == '1.0')
|
||||
@ -609,13 +625,24 @@ class calendar_rrule implements Iterator
|
||||
*
|
||||
* @param array $event
|
||||
* @param boolean $usertime=true true: event timestamps are usertime (default for calendar_bo::(read|search), false: servertime
|
||||
* @param string $to_tz timezone for exports (null for event's timezone)
|
||||
*
|
||||
* @return calendar_rrule
|
||||
*/
|
||||
public static function event2rrule(array $event,$usertime=true)
|
||||
public static function event2rrule(array $event,$usertime=true,$to_tz=null)
|
||||
{
|
||||
if (!$to_tz) $to_tz = $event['tzid'];
|
||||
$timestamp_tz = $usertime ? egw_time::$user_timezone : egw_time::$server_timezone;
|
||||
$time = is_a($event['start'],'DateTime') ? $event['start'] : new egw_time($event['start'],$timestamp_tz);
|
||||
$time->setTimezone(new DateTimeZone($event['tzid']));
|
||||
|
||||
if (!isset(self::$tz_cache[$to_tz]))
|
||||
{
|
||||
self::$tz_cache[$to_tz] = calendar_timezones::DateTimeZone($to_tz);
|
||||
}
|
||||
|
||||
self::rrule2tz($event, $time, $to_tz);
|
||||
|
||||
$time->setTimezone(self::$tz_cache[$to_tz]);
|
||||
|
||||
if ($event['recur_enddate'])
|
||||
{
|
||||
@ -646,6 +673,64 @@ class calendar_rrule implements Iterator
|
||||
'recur_exception' => $this->exceptions,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift a recurrence rule to a new timezone
|
||||
*
|
||||
* @param array $event recurring event
|
||||
* @param DateTime/string starttime of the event (in servertime)
|
||||
* @param string $to_tz new timezone
|
||||
*/
|
||||
public static function rrule2tz(array &$event,$starttime,$to_tz)
|
||||
{
|
||||
// We assume that the difference between timezones can result
|
||||
// in a maximum of one day
|
||||
|
||||
if (!is_array($event) ||
|
||||
!isset($event['recur_type']) ||
|
||||
$event['recur_type'] == MCAL_RECUR_NONE ||
|
||||
empty($event['recur_data']) || $event['recur_data'] == ALLDAYS ||
|
||||
empty($event['tzid']) || empty($to_tz) ||
|
||||
$event['tzid'] == $to_tz) return;
|
||||
|
||||
if (!isset(self::$tz_cache[$event['tzid']]))
|
||||
{
|
||||
self::$tz_cache[$event['tzid']] = calendar_timezones::DateTimeZone($event['tzid']);
|
||||
}
|
||||
if (!isset(self::$tz_cache[$to_tz]))
|
||||
{
|
||||
self::$tz_cache[$to_tz] = calendar_timezones::DateTimeZone($to_tz);
|
||||
}
|
||||
|
||||
$time = is_a($starttime,'DateTime') ?
|
||||
$starttime : new egw_time($starttime, egw_time::$server_timezone);
|
||||
$time->setTimezone(self::$tz_cache[$event['tzid']]);
|
||||
$remote = clone $time;
|
||||
$remote->setTimezone(self::$tz_cache[$to_tz]);
|
||||
$delta = (int)$remote->format('w') - (int)$time->format('w');
|
||||
if ($delta)
|
||||
{
|
||||
// We have to generate a shifted rrule
|
||||
switch ($event['recur_type'])
|
||||
{
|
||||
case self::MONTHLY_WDAY:
|
||||
case self::WEEKLY:
|
||||
$mask = (int)$event['recur_data'];
|
||||
|
||||
if ($delta == 1 || $delta == -6)
|
||||
{
|
||||
$mask = $mask << 1;
|
||||
if ($mask & 128) $mask = $mask - 127; // overflow
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($mask & 1) $mask = $mask + 128; // underflow
|
||||
$mask = $mask >> 1;
|
||||
}
|
||||
$event['recur_data'] = $mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__) // some tests
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -44,6 +44,7 @@ define('REJECTED',0);
|
||||
define('NO_RESPONSE',1);
|
||||
define('TENTATIVE',2);
|
||||
define('ACCEPTED',3);
|
||||
define('DELEGATED',4);
|
||||
|
||||
define('HOUR_s',60*60);
|
||||
define('DAY_s',24*HOUR_s);
|
||||
@ -157,6 +158,7 @@ class calendar_so
|
||||
// We want only the parents to match
|
||||
$where['cal_uid'] = $ids;
|
||||
$where['cal_reference'] = 0;
|
||||
$where['cal_recurrence'] = 0;
|
||||
}
|
||||
if ((int) $recur_date)
|
||||
{
|
||||
@ -187,6 +189,18 @@ class calendar_so
|
||||
$this->db->update($this->cal_table, array('cal_uid' => $event['uid']),
|
||||
array('cal_id' => $event['id']),__LINE__,__FILE__,'calendar');
|
||||
}
|
||||
if ((int) $recur_date == 0 &&
|
||||
$event['recur_type'] != MCAL_RECUR_NONE &&
|
||||
!empty($event['recur_exception']))
|
||||
{
|
||||
sort($event['recur_exception']);
|
||||
if ($event['recur_exception'][0] < $event['start'])
|
||||
{
|
||||
// leading exceptions => move start and end
|
||||
$event['end'] -= $event['start'] - $event['recur_exception'][0];
|
||||
$event['start'] = $event['recur_exception'][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if we have a real recurance, if not set $recur_date=0
|
||||
@ -358,7 +372,7 @@ class calendar_so
|
||||
'cal_user_type' => $type,
|
||||
'cal_user_id' => $ids,
|
||||
));
|
||||
if ($type == 'u' && ($filter == 'owner' || $filter == 'all'))
|
||||
if ($type == 'u' && ($filter == 'owner'))
|
||||
{
|
||||
$cal_table_def = $this->db->get_table_definitions('calendar',$this->cal_table);
|
||||
$to_or[] = $this->db->expression($cal_table_def,array('cal_owner' => $ids));
|
||||
@ -376,7 +390,10 @@ class calendar_so
|
||||
$where[] = "cal_status='T'"; break;
|
||||
case 'rejected':
|
||||
$where[] = "cal_status='R'"; break;
|
||||
case 'delegated':
|
||||
$where[] = "cal_status='D'"; break;
|
||||
case 'all':
|
||||
case 'owner':
|
||||
break;
|
||||
default:
|
||||
//if (!$show_rejected) // not longer used
|
||||
@ -443,18 +460,31 @@ class calendar_so
|
||||
$events = $ids = $recur_dates = $recur_ids = array();
|
||||
foreach($rs as $row)
|
||||
{
|
||||
$ids[] = $id = $row['cal_id'];
|
||||
$id = $row['cal_id'];
|
||||
if (is_numeric($id)) $ids[] = $id;
|
||||
|
||||
if ($row['cal_recur_date'])
|
||||
{
|
||||
$id .= '-'.$row['cal_recur_date'];
|
||||
$recur_dates[] = $row['cal_recur_date'];
|
||||
}
|
||||
if ($row['participants'])
|
||||
{
|
||||
$row['participants'] = explode(',',$row['participants']);
|
||||
$row['participants'] = array_combine($row['participants'],
|
||||
array_fill(0,count($row['participants']),''));
|
||||
}
|
||||
else
|
||||
{
|
||||
$row['participants'] = array();
|
||||
}
|
||||
$row['alarm'] = array();
|
||||
$row['recur_exception'] = $row['recur_exception'] ? explode(',',$row['recur_exception']) : array();
|
||||
|
||||
$events[$id] = egw_db::strip_array_keys($row,'cal_');
|
||||
}
|
||||
if (count($events))
|
||||
//_debug_array($events);
|
||||
if (count($ids))
|
||||
{
|
||||
// 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
|
||||
@ -576,6 +606,8 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
$minimum_uid_length = 8;
|
||||
}
|
||||
|
||||
$old_min = $old_duration = 0;
|
||||
|
||||
//echo '<p>'.__METHOD__.'('.array2string($event).",$change_since) event="; _debug_array($event);
|
||||
//error_log(__METHOD__.'('.array2string($event).",$set_recurrences,$change_since,$etag)");
|
||||
|
||||
@ -598,7 +630,19 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
unset($event[$col]);
|
||||
}
|
||||
}
|
||||
if (is_array($event['cal_category'])) $event['cal_category'] = implode(',',$event['cal_category']);
|
||||
// ensure that we find mathing entries later on
|
||||
if (!is_array($event['cal_category']))
|
||||
{
|
||||
$categories = array_unique(explode(',',$event['cal_category']));
|
||||
sort($categories);
|
||||
}
|
||||
else
|
||||
{
|
||||
$categories = array_unique($event['cal_category']);
|
||||
}
|
||||
sort($categories, SORT_NUMERIC);
|
||||
|
||||
$event['cal_category'] = implode(',',$categories);
|
||||
|
||||
if ($cal_id)
|
||||
{
|
||||
@ -643,15 +687,35 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
array('cal_id' => $event['cal_id']),__LINE__,__FILE__,'calendar');
|
||||
}
|
||||
// write information about recuring event, if recur_type is present in the array
|
||||
if (isset($event['recur_type']))
|
||||
if ($event['recur_type'] != MCAL_RECUR_NONE)
|
||||
{
|
||||
// fetch information about the currently saved (old) event
|
||||
$old_min = (int) $this->db->select($this->dates_table,'MIN(cal_start)',array('cal_id'=>$cal_id),__LINE__,__FILE__,false,'','calendar')->fetchColumn();
|
||||
$old_duration = (int) $this->db->select($this->dates_table,'MIN(cal_end)',array('cal_id'=>$cal_id),__LINE__,__FILE__,false,'','calendar')->fetchColumn() - $old_min;
|
||||
$old_repeats = $this->db->select($this->repeats_table,'*',array('cal_id' => $cal_id),__LINE__,__FILE__,false,'','calendar')->fetch();
|
||||
$old_exceptions = $old_repeats['recur_exception'] ? explode(',',$old_repeats['recur_exception']) : array();
|
||||
if (!empty($old_exceptions))
|
||||
{
|
||||
sort($old_exceptions);
|
||||
if ($old_min > $old_exceptions[0]) $old_min = $old_exceptions[0];
|
||||
}
|
||||
|
||||
$event['recur_exception'] = is_array($event['recur_exception']) ? $event['recur_exception'] : array();
|
||||
if (!empty($event['recur_exception']))
|
||||
{
|
||||
sort($event['recur_exception']);
|
||||
}
|
||||
|
||||
$where = array('cal_id' => $cal_id,
|
||||
'cal_recur_date' => 0);
|
||||
$old_participants = array();
|
||||
foreach ($this->db->select($this->user_table,'cal_user_type,cal_user_id,cal_status,cal_quantity,cal_role', $where,
|
||||
__LINE__,__FILE__,false,'','calendar') as $row)
|
||||
{
|
||||
$uid = self::combine_user($row['cal_user_type'], $row['cal_user_id']);
|
||||
$status = self::combine_status($row['cal_status'], $row['cal_quantity'], $row['cal_role']);
|
||||
$old_participants[$uid] = $status;
|
||||
}
|
||||
|
||||
// re-check: did so much recurrence data change that we have to rebuild it from scratch?
|
||||
if (!$set_recurrences)
|
||||
@ -680,18 +744,20 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
{
|
||||
// we adjust some possibly changed recurrences manually
|
||||
// deleted exceptions: re-insert recurrences into the user and dates table
|
||||
if(count($deleted_exceptions = array_diff($old_exceptions,$event['recur_exception'])))
|
||||
if (count($deleted_exceptions = array_diff($old_exceptions,$event['recur_exception'])))
|
||||
{
|
||||
if (isset($event['cal_participants']))
|
||||
{
|
||||
$participants = $event['cal_participants'];
|
||||
}
|
||||
else
|
||||
{
|
||||
// use old default
|
||||
$participants = $old_participants;
|
||||
}
|
||||
foreach($deleted_exceptions as $id => $deleted_exception)
|
||||
{
|
||||
// rebuild participants for the re-inserted recurrence
|
||||
$participants = array();
|
||||
$participants_only = $this->get_participants($cal_id); // participants without states
|
||||
foreach($participants_only as $id => $participant_only)
|
||||
{
|
||||
$states = $this->get_recurrences($cal_id, $participant_only['uid']);
|
||||
$participants[$participant_only['uid']] = $states[0]; // insert main status as default
|
||||
}
|
||||
$this->recurrence($cal_id, $deleted_exception, $deleted_exception + $old_duration, $participants);
|
||||
}
|
||||
}
|
||||
@ -742,7 +808,7 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
// update start- and endtime if present in the event-array, evtl. we need to move all recurrences
|
||||
if (isset($event['cal_start']) && isset($event['cal_end']))
|
||||
{
|
||||
$this->move($cal_id,$event['cal_start'],$event['cal_end'],!$cal_id ? false : $change_since);
|
||||
$this->move($cal_id,$event['cal_start'],$event['cal_end'],!$cal_id ? false : $change_since, $old_min, $old_min + $old_duration);
|
||||
}
|
||||
// update participants if present in the event-array
|
||||
if (isset($event['cal_participants']))
|
||||
@ -943,11 +1009,12 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
/**
|
||||
* updates the participants of an event, taken into account the evtl. recurrences of the event(!)
|
||||
* this method just adds new participants or removes not longer set participants
|
||||
* this method does never overwrite existing entries (except for delete)
|
||||
* this method does never overwrite existing entries (except the 0-recurrence and for delete)
|
||||
*
|
||||
* @param int $cal_id
|
||||
* @param array $participants uid => status pairs
|
||||
* @param int|boolean $change_since=0, false=new entry, 0=all, > 0 time from which on the repetitions should be changed
|
||||
* @param int|boolean $change_since=0, false=new event,
|
||||
* 0=all, > 0 time from which on the repetitions should be changed
|
||||
* @param boolean $add_only=false
|
||||
* false = add AND delete participants if needed (full list of participants required in $participants)
|
||||
* true = only add participants if needed, no participant will be deleted (participants to check/add required in $participants)
|
||||
@ -1033,7 +1100,7 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
// update participants
|
||||
foreach($participants as $uid => $status)
|
||||
{
|
||||
$id = null;
|
||||
$type = $id = $quantity = $role = null;
|
||||
self::split_user($uid,$type,$id);
|
||||
self::split_status($status,$quantity,$role);
|
||||
$set = array(
|
||||
@ -1072,7 +1139,8 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
REJECTED => 'R',
|
||||
NO_RESPONSE => 'U',
|
||||
TENTATIVE => 'T',
|
||||
ACCEPTED => 'A'
|
||||
ACCEPTED => 'A',
|
||||
DELEGATED => 'D'
|
||||
);
|
||||
if (!(int)$cal_id || !(int)$user_id && $user_type != 'e')
|
||||
{
|
||||
@ -1388,16 +1456,14 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
* get stati of all recurrences of an event for a specific participant
|
||||
*
|
||||
* @param int $cal_id
|
||||
* @param int $uid participant uid
|
||||
* @param int $uid=null participant uid; if == null return only the recur dates
|
||||
* @param int $start=0 if != 0: startdate of the search/list (servertime)
|
||||
* @param int $end=0 if != 0: enddate of the search/list (servertime)
|
||||
*
|
||||
* @return array recur_date => status pairs (index 0 => main status)
|
||||
*/
|
||||
function get_recurrences($cal_id, $uid, $start=0, $end=0)
|
||||
function get_recurrences($cal_id, $uid=null, $start=0, $end=0)
|
||||
{
|
||||
$user_type = $user_id = null;
|
||||
self::split_user($uid, $user_type, $user_id);
|
||||
$participant_status = array();
|
||||
$where = array('cal_id' => $cal_id);
|
||||
if ($start != 0 && $end == 0) $where[] = '(cal_recur_date = 0 OR cal_recur_date >= ' . (int)$start . ')';
|
||||
@ -1412,6 +1478,9 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
// inititalize the array
|
||||
$participant_status[$row['cal_recur_date']] = null;
|
||||
}
|
||||
if (is_null($uid)) return $participant_status;
|
||||
$user_type = $user_id = null;
|
||||
self::split_user($uid, $user_type, $user_id);
|
||||
$where = array(
|
||||
'cal_id' => $cal_id,
|
||||
'cal_user_type' => $user_type ? $user_type : 'u',
|
||||
@ -1478,10 +1547,10 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
foreach ($this->db->select($this->cal_table,'cal_id,cal_reference',$where,
|
||||
__LINE__,__FILE__,false,'','calendar') as $row)
|
||||
{
|
||||
if ($row['cal_reference'])
|
||||
if ($row['cal_reference'] != 0)
|
||||
{
|
||||
// not the series entry itself
|
||||
$related[$row['cal_id']] = $row['cal_reference'];
|
||||
// not the series master
|
||||
$related[] = $row['cal_id'];
|
||||
}
|
||||
}
|
||||
return $related;
|
||||
@ -1489,71 +1558,347 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
|
||||
/**
|
||||
* Gets the exception days of a given recurring event caused by
|
||||
* irregular participant stati
|
||||
* irregular participant stati or timezone transitions
|
||||
*
|
||||
* @param array $event Recurring Event.
|
||||
* @param string tz_id=null timezone for exports (null for event's timezone)
|
||||
* @param int $start=0 if != 0: startdate of the search/list (servertime)
|
||||
* @param int $end=0 if != 0: enddate of the search/list (servertime)
|
||||
* @param int $end=0 if != 0: enddate of the search/list (servertime)
|
||||
* @param string $filter='all' string filter-name: all (not rejected),
|
||||
* accepted, unknown, tentative, rejected, delegated
|
||||
* rrule return array of remote exceptions in servertime
|
||||
* tz_rrule/tz_only, return (only by) timezone transition affected entries
|
||||
* map return array of dates with no pseudo exception
|
||||
* key remote occurrence date
|
||||
* tz_map return array of all dates with no tz pseudo exception
|
||||
*
|
||||
* @return array Array of exception days (false for non-recurring events).
|
||||
*/
|
||||
function get_recurrence_exceptions($event, $tz_id=null, $start=0, $end=0)
|
||||
function get_recurrence_exceptions($event, $tz_id=null, $start=0, $end=0, $filter='all')
|
||||
{
|
||||
if (!is_array($event)) return false;
|
||||
$cal_id = (int) $event['id'];
|
||||
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
// "($cal_id, $tz_id, $filter): " . $event['tzid']);
|
||||
if (!$cal_id || $event['recur_type'] == MCAL_RECUR_NONE) return false;
|
||||
|
||||
$days = array();
|
||||
$first_start = $recur_start = '';
|
||||
|
||||
if (!empty($tz_id))
|
||||
$expand_all = (!$this->isWholeDay($event) && $tz_id && $tz_id != $event['tzid']);
|
||||
|
||||
if ($filter == 'tz_only' && !$expand_all) return $days;
|
||||
|
||||
$remote = in_array($filter, array('tz_rrule', 'rrule'));
|
||||
|
||||
$egw_rrule = calendar_rrule::event2rrule($event, false);
|
||||
$egw_rrule->current = clone $egw_rrule->time;
|
||||
if ($expand_all)
|
||||
{
|
||||
// set export timezone
|
||||
if(!isset(self::$tz_cache[$tz_id]))
|
||||
unset($event['recur_excpetion']);
|
||||
$remote_rrule = calendar_rrule::event2rrule($event, false, $tz_id);
|
||||
$remote_rrule->current = clone $remote_rrule->time;
|
||||
}
|
||||
while ($egw_rrule->valid())
|
||||
{
|
||||
while ($egw_rrule->exceptions &&
|
||||
in_array($egw_rrule->current->format('Ymd'),$egw_rrule->exceptions))
|
||||
{
|
||||
self::$tz_cache[$tz_id] = calendar_timezones::DateTimeZone($tz_id);
|
||||
if (in_array($filter, array('map','tz_map','rrule','tz_rrule')))
|
||||
{
|
||||
// real exception
|
||||
$locts = (int)egw_time::to($egw_rrule->current(),'server');
|
||||
if ($expand_all)
|
||||
{
|
||||
$remts = (int)egw_time::to($remote_rrule->current(),'server');
|
||||
if ($remote)
|
||||
{
|
||||
$days[$locts]= $remts;
|
||||
}
|
||||
else
|
||||
{
|
||||
$days[$remts]= $locts;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$days[$locts]= $locts;
|
||||
}
|
||||
}
|
||||
if ($expand_all)
|
||||
{
|
||||
$remote_rrule->next_no_exception();
|
||||
}
|
||||
$egw_rrule->next_no_exception();
|
||||
if (!$egw_rrule->valid()) return $days;
|
||||
}
|
||||
$starttime = new egw_time($event['start'], egw_time::$server_timezone);
|
||||
$starttime->setTimezone(self::$tz_cache[$tz_id]);
|
||||
$first_start = $starttime->format('His');
|
||||
$day = $egw_rrule->current();
|
||||
$locts = (int)egw_time::to($day,'server');
|
||||
$tz_exception = ($filter == 'tz_rrule');
|
||||
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
// '()[EVENT Server]: ' . $day->format('Ymd\THis') . " ($locts)");
|
||||
if ($expand_all)
|
||||
{
|
||||
$remote_day = $remote_rrule->current();
|
||||
$remts = (int)egw_time::to($remote_day,'server');
|
||||
// error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
// '()[EVENT Device]: ' . $remote_day->format('Ymd\THis') . " ($remts)");
|
||||
}
|
||||
|
||||
|
||||
if (!($end && $end < $locts) && $start <= $locts)
|
||||
{
|
||||
// we are within the relevant time period
|
||||
if ($expand_all && $day->format('U') != $remote_day->format('U'))
|
||||
{
|
||||
$tz_exception = true;
|
||||
if ($filter != 'map' && $filter != 'tz_map')
|
||||
{
|
||||
// timezone pseudo exception
|
||||
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
// '() tz exception: ' . $day->format('Ymd\THis'));
|
||||
if ($remote)
|
||||
{
|
||||
$days[$locts]= $remts;
|
||||
}
|
||||
else
|
||||
{
|
||||
$days[$remts]= $locts;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($filter != 'tz_map' && (!$tz_exception || $filter == 'tz_only') &&
|
||||
$this->status_pseudo_exception($event['id'], $locts, $filter))
|
||||
{
|
||||
// status pseudo exception
|
||||
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
// '() status exception: ' . $day->format('Ymd\THis'));
|
||||
if ($expand_all)
|
||||
{
|
||||
if ($filter == 'tz_only')
|
||||
{
|
||||
unset($days[$remts]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($filter != 'map')
|
||||
{
|
||||
if ($remote)
|
||||
{
|
||||
$days[$locts]= $remts;
|
||||
}
|
||||
else
|
||||
{
|
||||
$days[$remts]= $locts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($filter != 'map')
|
||||
{
|
||||
$days[$locts]= $locts;
|
||||
}
|
||||
}
|
||||
elseif (($filter == 'map' || filter == 'tz_map') &&
|
||||
!$tz_exception)
|
||||
{
|
||||
// no pseudo exception date
|
||||
if ($expand_all)
|
||||
{
|
||||
|
||||
$days[$remts]= $locts;
|
||||
}
|
||||
else
|
||||
{
|
||||
$days[$locts]= $locts;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($expand_all)
|
||||
{
|
||||
$remote_rrule->next_no_exception();
|
||||
}
|
||||
$egw_rrule->next_no_exception();
|
||||
}
|
||||
return $days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for status only pseudo exceptions
|
||||
*
|
||||
* @param int $cal_id event id
|
||||
* @param int $recur_date occurrence to check
|
||||
* @param string $filter status filter criteria for user
|
||||
*
|
||||
* @return boolean true, if stati don't match with defaults
|
||||
*/
|
||||
function status_pseudo_exception($cal_id, $recur_date, $filter)
|
||||
{
|
||||
static $recurrence_zero;
|
||||
static $cached_id;
|
||||
static $user;
|
||||
|
||||
if (!isset($cached_id) || $cached_id != $cal_id)
|
||||
{
|
||||
// get default stati
|
||||
$recurrence_zero = array();
|
||||
$user = $GLOBALS['egw_info']['user']['account_id'];
|
||||
$where = array('cal_id' => $cal_id,
|
||||
'cal_recur_date' => 0);
|
||||
foreach ($this->db->select($this->user_table,'cal_user_id,cal_user_type,cal_status',$where,
|
||||
__LINE__,__FILE__,false,'','calendar') as $row)
|
||||
{
|
||||
switch ($row['cal_user_type'])
|
||||
{
|
||||
case 'u': // account
|
||||
case 'c': // contact
|
||||
case 'e': // email address
|
||||
$uid = self::combine_user($row['cal_user_type'], $row['cal_user_id']);
|
||||
$recurrence_zero[$uid] = $row['cal_status'];
|
||||
}
|
||||
}
|
||||
$cached_id = $cal_id;
|
||||
}
|
||||
|
||||
$participants = $this->get_participants($event['id'], 0);
|
||||
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
// "($cal_id, $recur_date, $filter)[DEFAULTS]: " .
|
||||
// array2string($recurrence_zero));
|
||||
|
||||
// Check if the stati for all participants are identical for all recurrences
|
||||
foreach ($participants as $uid => $attendee)
|
||||
$participants = array();
|
||||
$where = array('cal_id' => $cal_id,
|
||||
'cal_recur_date' => $recur_date);
|
||||
foreach ($this->db->select($this->user_table,'cal_user_id,cal_user_type,cal_status',$where,
|
||||
__LINE__,__FILE__,false,'','calendar') as $row)
|
||||
{
|
||||
switch ($attendee['type'])
|
||||
switch ($row['cal_user_type'])
|
||||
{
|
||||
case 'u': // account
|
||||
case 'c': // contact
|
||||
case 'e': // email address
|
||||
$recurrences = $this->get_recurrences($event['id'], $uid, $start, $end);
|
||||
foreach ($recurrences as $recur_date => $recur_status)
|
||||
{
|
||||
if ($recur_date)
|
||||
{
|
||||
if (!empty($tz_id))
|
||||
{
|
||||
$time = new egw_time($recur_date, egw_time::$server_timezone);
|
||||
$time->setTimezone(self::$tz_cache[$tz_id]);
|
||||
$recur_start = $time->format('His');
|
||||
}
|
||||
if ($recur_status != $recurrences[0]
|
||||
|| !empty($tz_id) && $first_start != $recur_start)
|
||||
{
|
||||
// Every distinct status or starttime results in an exception
|
||||
$days[] = $recur_date;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: // We don't handle the rest
|
||||
break;
|
||||
$uid = self::combine_user($row['cal_user_type'], $row['cal_user_id']);
|
||||
$participants[$uid] = $row['cal_status'];
|
||||
}
|
||||
}
|
||||
$days = array_unique($days);
|
||||
sort($days);
|
||||
return $days;
|
||||
|
||||
if (empty($participants)) return false; // occurrence does not exist at all yet
|
||||
|
||||
foreach ($recurrence_zero as $uid => $status)
|
||||
{
|
||||
if ($uid == $user)
|
||||
{
|
||||
// handle filter for current user
|
||||
switch ($filter)
|
||||
{
|
||||
case 'unknown':
|
||||
if ($status != 'U')
|
||||
{
|
||||
unset($participants[$uid]);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 'accepted':
|
||||
if ($status != 'A')
|
||||
{
|
||||
unset($participants[$uid]);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 'tentative':
|
||||
if ($status != 'T')
|
||||
{
|
||||
unset($participants[$uid]);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 'rejected':
|
||||
if ($status != 'R')
|
||||
{
|
||||
unset($participants[$uid]);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 'delegated':
|
||||
if ($status != 'D')
|
||||
{
|
||||
unset($participants[$uid]);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case 'default':
|
||||
if ($status == 'R')
|
||||
{
|
||||
unset($participants[$uid]);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// All entries
|
||||
}
|
||||
}
|
||||
if (!isset($participants[$uid])
|
||||
|| $participants[$uid] != $status)
|
||||
return true;
|
||||
unset($participants[$uid]);
|
||||
}
|
||||
return (!empty($participants));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event is the whole day
|
||||
*
|
||||
* @param array $event event (all timestamps in servertime)
|
||||
* @return boolean true if whole day event within its timezone, false othwerwise
|
||||
*/
|
||||
function isWholeDay($event)
|
||||
{
|
||||
if (!isset($event['start']) || !isset($event['end'])) return false;
|
||||
|
||||
if (empty($event['tzid']))
|
||||
{
|
||||
$timezone = egw_time::$server_timezone;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isset(self::$tz_cache[$event['tzid']]))
|
||||
{
|
||||
self::$tz_cache[$event['tzid']] = calendar_timezones::DateTimeZone($event['tzid']);
|
||||
}
|
||||
$timezone = self::$tz_cache[$event['tzid']];
|
||||
}
|
||||
$start = new egw_time($event['start'],egw_time::$server_timezone);
|
||||
$start->setTimezone($timezone);
|
||||
$end = new egw_time($event['end'],egw_time::$server_timezone);
|
||||
$end->setTimezone($timezone);
|
||||
//error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
// '(): ' . $start . '-' . $end);
|
||||
$start = egw_time::to($start,'array');
|
||||
$end = egw_time::to($end,'array');
|
||||
|
||||
|
||||
return !$start['hour'] && !$start['minute'] && $end['hour'] == 23 && $end['minute'] == 59;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a datetime to the beginning of the day within timezone
|
||||
*
|
||||
* @param egw_time &time the datetime entry
|
||||
* @param string tz_id timezone
|
||||
*
|
||||
* @return DateTime
|
||||
*/
|
||||
function &startOfDay(egw_time $time, $tz_id)
|
||||
{
|
||||
if (empty($tz_id))
|
||||
{
|
||||
$timezone = egw_time::$server_timezone;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isset(self::$tz_cache[$tz_id]))
|
||||
{
|
||||
self::$tz_cache[$tz_id] = calendar_timezones::DateTimeZone($tz_id);
|
||||
}
|
||||
$timezone = self::$tz_cache[$tz_id];
|
||||
}
|
||||
return new egw_time($time->format('Y-m-d 00:00:00'), $timezone);
|
||||
}
|
||||
}
|
||||
|
@ -703,6 +703,7 @@ class calendar_ui
|
||||
'accepted' => array(lang('Accepted'), lang('Show only accepted events')),
|
||||
'unknown' => array(lang('Invitations'), lang('Show only invitations, not yet accepted or rejected')),
|
||||
'tentative' => array(lang('Tentative'), lang('Show only tentative accepted events')),
|
||||
'delegated' => array(lang('Delegated'), lang('Show only delegated events')),
|
||||
'rejected' => array(lang('Rejected'),lang('Show only rejected events')),
|
||||
'owner' => array(lang('Owner too'),lang('Show also events just owned by selected user')),
|
||||
'all' => array(lang('All incl. rejected'),lang('Show all status incl. rejected events')),
|
||||
|
@ -238,7 +238,7 @@ class calendar_uiforms extends calendar_ui
|
||||
$event['end']['hour'] = 23; $event['end']['minute'] = $event['end']['second'] = 59; unset($event['end']['raw']);
|
||||
$event['end'] = $this->bo->date2ts($event['end']);
|
||||
}
|
||||
// some checks for recurances, if you give a date, make it a weekly repeating event and visa versa
|
||||
// some checks for recurrences, if you give a date, make it a weekly repeating event and visa versa
|
||||
if ($event['recur_type'] == MCAL_RECUR_NONE && $event['recur_data']) $event['recur_type'] = MCAL_RECUR_WEEKLY;
|
||||
if ($event['recur_type'] == MCAL_RECUR_WEEKLY && !$event['recur_data'])
|
||||
{
|
||||
@ -579,6 +579,15 @@ class calendar_uiforms extends calendar_ui
|
||||
case 'delete':
|
||||
if ($this->bo->delete($event['id'],(int)$content['edit_single']))
|
||||
{
|
||||
if ($content['reference'] == 0 && !$content['edit_single'])
|
||||
{
|
||||
// We delete a whole series
|
||||
$recur_exceptions = $this->bo->so->get_related($event['uid']);
|
||||
foreach ($recur_exceptions as $id)
|
||||
{
|
||||
$this->bo->delete($id);
|
||||
}
|
||||
}
|
||||
$msg = lang('Event deleted');
|
||||
$js = 'opener.location.href=\''.addslashes(egw::link($referer,array(
|
||||
'msg' => $msg,
|
||||
@ -824,14 +833,11 @@ class calendar_uiforms extends calendar_ui
|
||||
// check if we should create an exception
|
||||
if ($_GET['exception'])
|
||||
{
|
||||
// exception: preserv participants of this event and merge it with the 0-recurrence
|
||||
$participants = array('participants' => $event['participants'],'participant_types' => $event['participant_types']);
|
||||
$event = array_merge($this->bo->read($cal_id,0,true),$participants);
|
||||
$msg = $this->_create_exception($event,$preserv);
|
||||
}
|
||||
else
|
||||
{
|
||||
$event = $this->bo->read($cal_id,0,true); // read the 0-recurrence
|
||||
$event = $this->bo->read($cal_id,0,true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
calendar/templates/default/images/forward.gif
Normal file
BIN
calendar/templates/default/images/forward.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 319 B |
BIN
calendar/templates/default/images/forward.png
Normal file
BIN
calendar/templates/default/images/forward.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 494 B |
Loading…
Reference in New Issue
Block a user