mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-01-05 21:49:28 +01:00
Calendar synchronization backport
This commit is contained in:
parent
79a2c95678
commit
6433df94ec
@ -101,13 +101,14 @@ class calendar_bo
|
||||
'R' => 'Rejected',
|
||||
'T' => 'Tentative',
|
||||
'U' => 'No Response',
|
||||
'D' => 'Delegated',
|
||||
'G' => 'Group invitation',
|
||||
);
|
||||
/**
|
||||
* @var array recur_types translates MCAL recur-types to verbose labels
|
||||
*/
|
||||
var $recur_types = Array(
|
||||
MCAL_RECUR_NONE => 'None',
|
||||
MCAL_RECUR_NONE => 'No recurrence',
|
||||
MCAL_RECUR_DAILY => 'Daily',
|
||||
MCAL_RECUR_WEEKLY => 'Weekly',
|
||||
MCAL_RECUR_MONTHLY_WDAY => 'Monthly (by day)',
|
||||
@ -166,7 +167,7 @@ class calendar_bo
|
||||
/**
|
||||
* Instance of the categories class
|
||||
*
|
||||
* @var $categories
|
||||
* @var categories
|
||||
*/
|
||||
var $categories;
|
||||
|
||||
@ -290,9 +291,9 @@ class calendar_bo
|
||||
* filter string filter-name, atm. 'all' 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,
|
||||
@ -399,7 +400,7 @@ class calendar_bo
|
||||
}
|
||||
// date2ts(,true) converts to server time, db2data converts again to user-time
|
||||
$events =& $this->so->search(isset($start) ? $this->date2ts($start,true) : null,isset($end) ? $this->date2ts($end,true) : null,
|
||||
$users,$cat_id,$filter,$params['query'],$offset,(int)$params['num_rows'],$params['order'],$show_rejected,$params['cols'],$params['append']);
|
||||
$users,$cat_id,$filter,$params['query'],$offset,(int)$params['num_rows'],$params['order'],$show_rejected,$params['cols'],$params['append'],$params['cfs']);
|
||||
|
||||
if (isset($params['cols']))
|
||||
{
|
||||
@ -555,7 +556,7 @@ class calendar_bo
|
||||
$old_horizont = $this->config['horizont'];
|
||||
$this->config['horizont'] = $new_horizont;
|
||||
|
||||
// create further recurances 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)
|
||||
@ -576,10 +577,10 @@ class calendar_bo
|
||||
}
|
||||
|
||||
/**
|
||||
* set all recurances for an event til the defined horizont $this->config['horizont']
|
||||
* set all recurrences for an event until the defined horizont $this->config['horizont']
|
||||
*
|
||||
* @param array $event
|
||||
* @param mixed $start=0 minimum start-time for new recurances or !$start = since the start of the event
|
||||
* @param mixed $start=0 minimum start-time for new recurrences or !$start = since the start of the event
|
||||
*/
|
||||
function set_recurrences($event,$start=0)
|
||||
{
|
||||
@ -587,11 +588,19 @@ class calendar_bo
|
||||
{
|
||||
$this->debug_message('bocal::set_recurrences(%1,%2)',true,$event,$start);
|
||||
}
|
||||
// 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'];
|
||||
|
||||
@ -679,11 +688,11 @@ class calendar_bo
|
||||
/**
|
||||
* Reads a calendar-entry
|
||||
*
|
||||
* @param int/array/string $ids id or array of id's of the entries to read, or string with a single uid
|
||||
* @param int|array|string $ids id or array of id's of the entries to read, or string with a single uid
|
||||
* @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')
|
||||
{
|
||||
@ -747,9 +756,9 @@ class calendar_bo
|
||||
*/
|
||||
function insert_all_repetitions($event,$start,$end,&$events,$recur_exceptions)
|
||||
{
|
||||
if ((int) $this->debug >= 3 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repitions')
|
||||
if ((int) $this->debug >= 3 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repetions')
|
||||
{
|
||||
$this->debug_message('bocal::insert_all_repitions(%1,%2,%3,&$event,%4)',true,$event,$start,$end,$recur_exceptions);
|
||||
$this->debug_message(__METHOD__.'(%1,%2,%3,&$event,%4)',true,$event,$start,$end,$recur_exceptions);
|
||||
}
|
||||
$start_in = $start; $end_in = $end;
|
||||
|
||||
@ -758,9 +767,9 @@ class calendar_bo
|
||||
$event_start_ts = $this->date2ts($event['start']);
|
||||
$event_end_ts = $this->date2ts($event['end']);
|
||||
|
||||
if ($this->debug && ((int) $this->debug > 3 || $this->debug == 'insert_all_repetions' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repitions'))
|
||||
if ($this->debug && ((int) $this->debug > 3 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repetions'))
|
||||
{
|
||||
$this->debug_message('bocal::insert_all_repetions(%1,start=%2,end=%3,,%4) starting...',True,$event,$start_in,$end_in,$recur_exceptions);
|
||||
$this->debug_message(__METHOD__.'(%1,start=%2,end=%3,,%4) starting...',True,$event,$start_in,$end_in,$recur_exceptions);
|
||||
}
|
||||
$id = $event['id'];
|
||||
$event_start_arr = $this->date2array($event['start']);
|
||||
@ -800,9 +809,9 @@ class calendar_bo
|
||||
if (($have_exception = $search_date_ymd == (int)$this->date2string($exception_ts))) break;
|
||||
}
|
||||
}
|
||||
if ($this->debug && ((int) $this->debug > 3 || $this->debug == 'insert_all_repetions' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repitions'))
|
||||
if ($this->debug && ((int) $this->debug > 3 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repetions'))
|
||||
{
|
||||
$this->debug_message('bocal::insert_all_repetions(...,%1) checking recur_exceptions[%2] and event[recur_exceptions]=%3 ==> %4',False,
|
||||
$this->debug_message(__METHOD__.'(...,%1) checking recur_exceptions[%2] and event[recur_exceptions]=%3 ==> %4',False,
|
||||
$recur_exceptions,$search_date_ymd,$event['recur_exception'],$have_exception);
|
||||
}
|
||||
if ($have_exception)
|
||||
@ -914,18 +923,18 @@ class calendar_bo
|
||||
break;
|
||||
} // switch(recur-type)
|
||||
} // for($date = ...)
|
||||
if ($this->debug && ((int) $this->debug > 2 || $this->debug == 'insert_all_repetions' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repitions'))
|
||||
if ($this->debug && ((int) $this->debug > 2 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repetions'))
|
||||
{
|
||||
$this->debug_message('bocal::insert_all_repetions(%1,start=%2,end=%3,events,exections=%4) events=%5',True,$event,$start_in,$end_in,$recur_exceptions,$events);
|
||||
$this->debug_message(__METHOD__.'(%1,start=%2,end=%3,events,exections=%4) events=%5',True,$event,$start_in,$end_in,$recur_exceptions,$events);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one repetion of $event for $date_ymd to the $events array, after adjusting its start- and end-time
|
||||
*
|
||||
* @param $events array in which the event gets inserted
|
||||
* @param $event array event to insert, it has start- and end-date of the first recurrence, not of $date_ymd
|
||||
* @param $date_ymd int/string of the date of the event
|
||||
* @param array $events in which the event gets inserted
|
||||
* @param array $event event to be inserted; it has start- and end-date of the first recurrence, not of $date_ymd
|
||||
* @param int|string $date_ymd of the date of the event
|
||||
*/
|
||||
function add_adjusted_event(&$events,$event,$date_ymd)
|
||||
{
|
||||
@ -1092,13 +1101,13 @@ class calendar_bo
|
||||
/**
|
||||
* Converts several date-types to a timestamp and optionaly converts user- to server-time
|
||||
*
|
||||
* @param $date mixed date to convert, should be one of the following types
|
||||
* @param mixed $date date to convert, should be one of the following types
|
||||
* 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 $user2server_time boolean conversation between user- and server-time default False == Off
|
||||
* @param boolean $user2server_time conversation between user- and server-time; default false == Off
|
||||
*/
|
||||
function date2ts($date,$user2server=False)
|
||||
function date2ts($date,$user2server=false)
|
||||
{
|
||||
$date_in = $date;
|
||||
|
||||
@ -1173,11 +1182,11 @@ class calendar_bo
|
||||
/**
|
||||
* Converts a date to an array and optionaly converts server- to user-time
|
||||
*
|
||||
* @param $date mixed date to convert
|
||||
* @param $server2user_time boolean conversation between user- and server-time default False == Off
|
||||
* @param mixed $date date to convert
|
||||
* @param boolean $server2user_time=false conversation between user- and server-time; default false == Off
|
||||
* @return array with keys 'second', 'minute', 'hour', 'day', 'month', 'year', 'raw' (timestamp) and 'full' (Ymd-string)
|
||||
*/
|
||||
function date2array($date,$server2user=False)
|
||||
function date2array($date,$server2user=false)
|
||||
{
|
||||
$date_called = $date;
|
||||
|
||||
@ -1254,7 +1263,7 @@ class calendar_bo
|
||||
* Formats a date given as timestamp or array
|
||||
*
|
||||
* @param mixed $date integer timestamp or array with ('year','month',..,'second') to convert
|
||||
* @param string/boolean $format='' default common_prefs[dateformat], common_prefs[timeformat], false=time only, true=date only
|
||||
* @param string|boolean $format='' default common_prefs[dateformat], common_prefs[timeformat], false=time only, true=date only
|
||||
* @return string the formated date (incl. time)
|
||||
*/
|
||||
function format_date($date,$format='')
|
||||
@ -1288,13 +1297,13 @@ class calendar_bo
|
||||
*
|
||||
* The parameters get formated depending on their type. ACL-values need a ACL_TYPE_IDENTIFER prefix.
|
||||
*
|
||||
* @param $msg string message with parameters/variables like lang(), eg. '%1'
|
||||
* @param $backtrace include a function-backtrace, default True=On
|
||||
* @param string $msg message with parameters/variables like lang(), eg. '%1'
|
||||
* @param boolean $backtrace=true include a function-backtrace, default true=On
|
||||
* should only be set to False=Off, if your code ensures a call with backtrace=On was made before !!!
|
||||
* @param $param mixed a variable number of parameters, to be inserted in $msg
|
||||
* @param mixed $param a variable number of parameters, to be inserted in $msg
|
||||
* arrays get serialized with print_r() !
|
||||
*/
|
||||
function debug_message($msg,$backtrace=True)
|
||||
function debug_message($msg,$backtrace=true)
|
||||
{
|
||||
static $acl2string = array(
|
||||
0 => 'ACL-UNKNOWN',
|
||||
@ -1473,7 +1482,7 @@ class calendar_bo
|
||||
/**
|
||||
* Converts a participant into a (readable) user- or resource-name
|
||||
*
|
||||
* @param $id string|int id of user or resource
|
||||
* @param string|int $id id of user or resource
|
||||
* @return string with name
|
||||
*/
|
||||
function participant_name($id,$use_type=false)
|
||||
@ -1534,6 +1543,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].')';
|
||||
@ -1571,8 +1583,8 @@ class calendar_bo
|
||||
/**
|
||||
* Converts category string of an event into array of (readable) category-names
|
||||
*
|
||||
* @param $category string cat-id (multiple id's commaseparated)
|
||||
* @param $color int color of the category, if multiple cats, the color of the last one with color is returned
|
||||
* @param string $category cat-id (multiple id's commaseparated)
|
||||
* @param int $color color of the category, if multiple cats, the color of the last one with color is returned
|
||||
* @return array with id / names
|
||||
*/
|
||||
function categories($category,&$color)
|
||||
@ -1656,7 +1668,7 @@ class calendar_bo
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the recure-information of an event, into a human readable string
|
||||
* Convert the recurrence-information of an event, into a human readable string
|
||||
*
|
||||
* @param array $event
|
||||
* @return string
|
||||
@ -1708,7 +1720,7 @@ class calendar_bo
|
||||
*
|
||||
* The holidays get cached in the session (performance), so changes in holidays or birthdays do NOT affect a current session!!!
|
||||
*
|
||||
* @param integer $year=0 year, defaults to 0 = current year
|
||||
* @param int $year=0 year, defaults to 0 = current year
|
||||
* @return array indexed with Ymd of array of holidays. A holiday is an array with the following fields:
|
||||
* index: numerical unique id
|
||||
* locale: string, 2-char short for the nation
|
||||
@ -1775,8 +1787,8 @@ class calendar_bo
|
||||
*
|
||||
* Is called as hook to participate in the linking
|
||||
*
|
||||
* @param int/array $entry int cal_id or array with event
|
||||
* @param string/boolean string with title, null if not found or false if not read perms
|
||||
* @param int|array $entry int cal_id or array with event
|
||||
* @param string|boolean string with title, null if not found or false if not read perms
|
||||
*/
|
||||
function link_title($event)
|
||||
{
|
||||
@ -1797,7 +1809,7 @@ class calendar_bo
|
||||
* Is called as hook to participate in the linking
|
||||
*
|
||||
* @param string $pattern pattern to search
|
||||
* @return array with pm_id - title pairs of the matching entries
|
||||
* @return array with cal_id - title pairs of the matching entries
|
||||
*/
|
||||
function link_query($pattern)
|
||||
{
|
||||
@ -1883,7 +1895,7 @@ class calendar_bo
|
||||
/**
|
||||
* Get the freebusy URL of a user
|
||||
*
|
||||
* @param int/string $user account_id or account_lid
|
||||
* @param int|string $user account_id or account_lid
|
||||
* @param string $pw=null password
|
||||
*/
|
||||
static function freebusy_url($user,$pw=null)
|
||||
@ -1900,13 +1912,14 @@ class calendar_bo
|
||||
* Check if the event is the whole day
|
||||
*
|
||||
* @param event
|
||||
* @param boolean $server2user_time=false conversation between user- and server-time; default false == Off
|
||||
* @return boolean true for whole day events
|
||||
*/
|
||||
function isWholeDay($event)
|
||||
function isWholeDay($event, $server2user=false)
|
||||
{
|
||||
// check if the event is the whole day
|
||||
$start = $this->date2array($event['start']);
|
||||
$end = $this->date2array($event['end']);
|
||||
$start = $this->date2array($event['start'], $server2user);
|
||||
$end = $this->date2array($event['end'], $server2user);
|
||||
$result = (!$start['hour'] && !$start['minute']
|
||||
&& $end['hour'] == 23 && $end['minute'] == 59);
|
||||
return $result;
|
||||
|
@ -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)
|
||||
@ -51,6 +52,13 @@ class calendar_boupdate extends calendar_bo
|
||||
*/
|
||||
var $debug;
|
||||
|
||||
/**
|
||||
* Set Logging
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
var $log = false;
|
||||
|
||||
/**
|
||||
* @var string|boolean $log_file filename to enable the login or false for no update-logging
|
||||
*/
|
||||
@ -97,29 +105,35 @@ class calendar_boupdate extends calendar_bo
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$event['id']) // some defaults for new entries
|
||||
if (($new_event = !$event['id'])) // some defaults for new entries
|
||||
{
|
||||
// if no owner given, set user to owner
|
||||
if (!$event['owner']) $event['owner'] = $this->user;
|
||||
// set owner as participant if none is given
|
||||
if (!is_array($event['participants']) || !count($event['participants']))
|
||||
{
|
||||
$event['participants'] = array($event['owner'] => 'U');
|
||||
}
|
||||
// set the status of the current user to 'A' = accepted
|
||||
if (isset($event['participants'][$this->user]) && $event['participants'][$this->user][0] != 'A')
|
||||
{
|
||||
$event['participants'][$this->user][0] = 'A';
|
||||
$status = $event['owner'] == $this->user ? 'A' : 'U';
|
||||
$status = calendar_so::combine_status($status, 1, 'CHAIR');
|
||||
$event['participants'] = array($event['owner'] => $status);
|
||||
}
|
||||
}
|
||||
|
||||
// check if user has the permission to update / create the event
|
||||
if (!$ignore_acl && ($event['id'] && !$this->check_perms(EGW_ACL_EDIT,$event['id']) ||
|
||||
!$event['id'] && !$this->check_perms(EGW_ACL_EDIT,0,$event['owner'])) &&
|
||||
if (!$ignore_acl && (!$new_event && !$this->check_perms(EGW_ACL_EDIT,$event['id']) ||
|
||||
$new_event && !$this->check_perms(EGW_ACL_EDIT,0,$event['owner'])) &&
|
||||
!$this->check_perms(EGW_ACL_ADD,0,$event['owner']))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$new_event)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
// check for conflicts only happens !$ignore_conflicts AND if start + end date are given
|
||||
if (!$ignore_conflicts && !$event['non_blocking'] && isset($event['start']) && isset($event['end']))
|
||||
{
|
||||
@ -250,14 +264,6 @@ class calendar_boupdate extends calendar_bo
|
||||
$event['modified'] = $this->now_su; // we are still in user-time
|
||||
$event['modifier'] = $GLOBALS['egw_info']['user']['account_id'];
|
||||
}
|
||||
if (!($new_event = !(int)$event['id']))
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
//echo "saving $event[id]="; _debug_array($event);
|
||||
$event2save = $event;
|
||||
|
||||
@ -359,7 +365,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'])
|
||||
{
|
||||
@ -485,6 +491,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';
|
||||
@ -738,7 +750,7 @@ class calendar_boupdate extends calendar_bo
|
||||
*/
|
||||
function check_status_perms($uid,$event)
|
||||
{
|
||||
if ($uid[0] == 'c' || $uid['0'] == 'e') // for contact we use the owner of the event
|
||||
if ($uid[0] == 'c' || $uid[0] == 'e') // for contact we use the owner of the event
|
||||
{
|
||||
if (!is_array($event) && !($event = $this->read($event))) return false;
|
||||
|
||||
@ -781,6 +793,7 @@ class calendar_boupdate extends calendar_bo
|
||||
'R' => MSG_REJECTED,
|
||||
'T' => MSG_TENTATIVE,
|
||||
'A' => MSG_ACCEPTED,
|
||||
'D' => MSG_DELEGATED,
|
||||
);
|
||||
if (isset($status2msg[$status]))
|
||||
{
|
||||
@ -802,8 +815,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))
|
||||
{
|
||||
@ -828,7 +839,7 @@ class calendar_boupdate extends calendar_bo
|
||||
}
|
||||
if ($event['reference'])
|
||||
{
|
||||
// evtl. delete recur_exception $event['recurrence'] from event with cal_id=$event['reference']
|
||||
// evtl. delete recur_exception $event['reference'] from event with cal_id=$event['reference']
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -1138,110 +1149,597 @@ 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']))
|
||||
$recur_date = 0;
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
if ($event['uid'] && ($uidmatch = $this->read($event['uid'])))
|
||||
{
|
||||
if ($event['reference'])
|
||||
if (isset($event['reference']))
|
||||
{
|
||||
// Let's try to find a real exception first
|
||||
$query['cal_uid'] = $event['uid'];
|
||||
$query['cal_reference'] = $event['reference'];
|
||||
|
||||
if ($foundEvents = parent::search(array(
|
||||
'query' => $query,
|
||||
)))
|
||||
{
|
||||
if(is_array($foundEvents))
|
||||
{
|
||||
$event = array_shift($foundEvents);
|
||||
return $event['id'];
|
||||
}
|
||||
}
|
||||
// Let's try the "status only" (pseudo) exceptions now
|
||||
if (($egw_event = $this->read($uidmatch['id'], $event['reference'])))
|
||||
{
|
||||
// Do we work with a pseudo exception here?
|
||||
$match = true;
|
||||
foreach (array('start', 'end', 'title', 'priority',
|
||||
'location', 'public', 'non_blocking') as $key)
|
||||
{
|
||||
if (isset($event[$key])
|
||||
&& $event[$key] != $egw_event[$key])
|
||||
{
|
||||
$match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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['reference']);
|
||||
|
||||
return false; // We need to create a new pseudo exception
|
||||
}
|
||||
$recur_date = $event['reference'];
|
||||
}
|
||||
else
|
||||
elseif (isset($event['start']))
|
||||
{
|
||||
return $uidmatch['id'];
|
||||
$recur_date = $event['start'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($event['id'] && ($found = $this->read($event['id'])))
|
||||
if ($event['id'])
|
||||
{
|
||||
// We only do a simple consistency check
|
||||
if ($found['title'] == $event['title']
|
||||
&& $found['start'] == $event['start']
|
||||
&& $found['end'] == $event['end'])
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'(' . $event['id'] . ")[EventID]");
|
||||
}
|
||||
if (($egwEvent = $this->read($event['id'], $recur_date, false, 'server')))
|
||||
{
|
||||
if ($this->log)
|
||||
{
|
||||
return $found['id'];
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'()[FOUND]:' . array2string($egwEvent));
|
||||
}
|
||||
// Just a simple consistency check
|
||||
if ($filter == 'exact' ||
|
||||
$filter == 'master' && $egwEvent['recur_type'] != MCAL_RECUR_NONE ||
|
||||
$filter != 'master' && strpos($egwEvent['title'], $event['title']) === 0)
|
||||
{
|
||||
$retval = $egwEvent['id'];
|
||||
if ($egwEvent['recur_type'] != MCAL_RECUR_NONE &&
|
||||
$event['recur_type'] == MCAL_RECUR_NONE && $event['reference'] != 0)
|
||||
{
|
||||
$retval .= ':' . (int)$event['reference'];
|
||||
}
|
||||
$matchingEvents[] = $retval;
|
||||
return $matchingEvents;
|
||||
}
|
||||
}
|
||||
if ($filter == 'exact') return array();
|
||||
}
|
||||
unset($event['id']);
|
||||
|
||||
if($foundEvents = parent::search(array(
|
||||
'query' => $query,
|
||||
)))
|
||||
if ($filter == 'master')
|
||||
{
|
||||
if(is_array($foundEvents))
|
||||
$query[] = 'recur_type!='. MCAL_RECUR_NONE;
|
||||
$query['cal_reference'] = 0;
|
||||
}
|
||||
|
||||
// only query calendars of users, we have READ-grants from
|
||||
$users = array();
|
||||
foreach(array_keys($this->grants) as $user)
|
||||
{
|
||||
$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', 'reference');
|
||||
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 ($filter != 'master' && isset($event['reference']))
|
||||
{
|
||||
$query['cal_reference'] = $event['reference'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->log)
|
||||
{
|
||||
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
|
||||
'[QUERY]: ' . array2string($query));
|
||||
}
|
||||
if (!count($users) || !($foundEvents =
|
||||
$this->so->search(null, null, $users, 0, 'all', $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;
|
||||
|
||||
if (in_array($filter, array('exact', 'master')) && !empty($event['uid']))
|
||||
{
|
||||
$matchingEvents[] = $egwEvent['id']; // UID found
|
||||
if ($filter = 'master') break;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check times
|
||||
if ($filter != 'relax')
|
||||
{
|
||||
if (isset($event['whole_day'])&& $event['whole_day'])
|
||||
{
|
||||
if (!$this->isWholeDay($egwEvent, true))
|
||||
{
|
||||
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
|
||||
$start = $this->date2ts($event['start'], true);
|
||||
$pseudos[] = $egwEvent['id'] . ':' . $start;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
elseif ($filter != 'relax')
|
||||
{
|
||||
// check exceptions
|
||||
// $exceptions[$remote_ts] = $egw_ts
|
||||
$exceptions = $this->so->get_recurrence_exceptions($egwEvent);
|
||||
$exceptions = array_merge($egwEvent['recur_exception'], $exceptions);
|
||||
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);
|
||||
}
|
||||
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)))
|
||||
{
|
||||
if (isset($stored_event['id']) && $master_event['id'] != $stored_event['id'])
|
||||
{
|
||||
$type = 'SERIES-EXCEPTION'; // this is an existing exception
|
||||
break;
|
||||
}
|
||||
elseif (isset($event['reference']) &&
|
||||
in_array($event['reference'], $master_event['recur_exception']))
|
||||
{
|
||||
$type = 'SERIES-PSEUDO-EXCEPTION'; // could also be a real one
|
||||
$recurrence_event = $master_event;
|
||||
$recurrence_event['start'] = $event['reference'];
|
||||
$recurrence_event['end'] -= $master_event['start'] - $event['reference'];
|
||||
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
|
||||
$recur_date = $this->date2usertime($event['start']);
|
||||
$egwEvent = $this->read($eventID, $recur_date, false, 'server');
|
||||
if ($event['start'] == $egwEvent['start'])
|
||||
{
|
||||
$type = 'SERIES-PSEUDO-EXCEPTION'; // let's try a pseudo exception
|
||||
$recurrence_event = $master_event;
|
||||
$recurrence_event['start'] = $event['start'];
|
||||
$recurrence_event['end'] -= $master_event['start'] - $event['start'];
|
||||
break;
|
||||
|
||||
}
|
||||
$recur_date = $this->date2usertime($event['reference']);
|
||||
$egwEvent = $this->read($eventID,$recur_date , false, 'server');
|
||||
if (isset($event['reference']) && $event['reference'] == $egwEvent['start'])
|
||||
{
|
||||
$type = 'SERIES-EXCEPTION-PROPAGATE';
|
||||
if ($stored_event)
|
||||
{
|
||||
unset($stored_event['id']); // signal the true exception
|
||||
$stored_event['recur_type'] = MCAL_RECUR_NONE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
}
|
@ -33,9 +33,25 @@ class calendar_groupdav extends groupdav_handler
|
||||
//'RDATE' => 'cal_start',
|
||||
//'EXRULE'
|
||||
//'EXDATE'
|
||||
'RECURRENCE-ID' => 'cal_reference',
|
||||
//'RECURRENCE-ID' => 'cal_reference',
|
||||
);
|
||||
|
||||
/**
|
||||
* Does client understand exceptions to be included in VCALENDAR component of series master sharing its UID
|
||||
*
|
||||
* That also means no EXDATE for these exceptions!
|
||||
*
|
||||
* Setting it to false, should give the old behavior used in 1.6 (hopefully) no client needs that.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
var $client_shared_uid_exceptions = true;
|
||||
|
||||
/**
|
||||
* Are we using id or uid for the path/url
|
||||
*/
|
||||
const PATH_ATTRIBUTE = 'id';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -50,8 +66,6 @@ class calendar_groupdav extends groupdav_handler
|
||||
$this->bo = new calendar_boupdate();
|
||||
}
|
||||
|
||||
const PATH_ATTRIBUTE = 'id';
|
||||
|
||||
/**
|
||||
* Create the path for an event
|
||||
*
|
||||
@ -84,11 +98,13 @@ class calendar_groupdav extends groupdav_handler
|
||||
*/
|
||||
function propfind($path,$options,&$files,$user,$id='')
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."($path,".array2string($options).",,$user,$id)");
|
||||
//error_log(__METHOD__."($path,".array2string($options).",,$user,$id)");//njv:
|
||||
if ($this->debug)
|
||||
{
|
||||
error_log(__METHOD__."($path,".array2string($options).",,$user,$id)");
|
||||
$starttime = microtime(true);
|
||||
}
|
||||
|
||||
// ToDo: add parameter to only return id & etag
|
||||
//error_log( __FILE__ . __METHOD__ ." :$user ". print_r($options,true));
|
||||
$st = microtime(true);
|
||||
$cal_filters = array(
|
||||
'users' => $user,
|
||||
'start' => time()-100*24*3600, // default one month back -30 breaks all sync recurrences
|
||||
@ -97,13 +113,23 @@ class calendar_groupdav extends groupdav_handler
|
||||
'daywise' => false,
|
||||
'date_format' => 'server',
|
||||
);
|
||||
if ($this->debug > 1) error_log(__METHOD__."($path,,,$user,$id) cal_filters=".array2string($cal_filters));
|
||||
//error_log(__METHOD__."($path,,,$user,$id) cal_filters=".array2string($cal_filters));//njv
|
||||
/*
|
||||
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));
|
||||
}
|
||||
|
||||
// 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']))
|
||||
{
|
||||
@ -116,11 +142,28 @@ class calendar_groupdav extends groupdav_handler
|
||||
}
|
||||
}
|
||||
}
|
||||
//error_log(__FILE__ . __METHOD__ ."Filters:" .print_r($cal_filters,true));
|
||||
if (($events = $this->bo->search($cal_filters)))
|
||||
$events =& $this->bo->search($cal_filters);
|
||||
if ($events)
|
||||
{
|
||||
foreach($events as $event)
|
||||
// get all max user modified times at once
|
||||
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['reference'], $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);
|
||||
|
||||
foreach($events as &$event)
|
||||
{
|
||||
$event['max_user_modified'] = $max_user_modified[$event['id']];
|
||||
//header('X-EGROUPWARE-EVENT-'.$event['id'].': '.$event['title'].': '.date('Y-m-d H:i:s',$event['start']).' - '.date('Y-m-d H:i:s',$event['end']));
|
||||
$props = array(
|
||||
HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($event)),
|
||||
@ -133,8 +176,7 @@ class calendar_groupdav extends groupdav_handler
|
||||
//error_log(__FILE__ . __METHOD__ . "Calendar Data : $calendar_data");
|
||||
if ($calendar_data)
|
||||
{
|
||||
if (is_null($handler)) $handler = $this->_get_handler();
|
||||
$content = $handler->exportVCal(array($event),'2.0','PUBLISH');
|
||||
$content = $this->iCal($event);
|
||||
$props[] = HTTP_WebDAV_Server::mkprop('getcontentlength',bytes($content));
|
||||
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-data',$content);
|
||||
}
|
||||
@ -148,8 +190,11 @@ class calendar_groupdav extends groupdav_handler
|
||||
);
|
||||
}
|
||||
}
|
||||
$end = microtime(true) - $st;
|
||||
if ($this->debug) error_log(__FILE__ . __METHOD__ . "Function took : $end");
|
||||
if ($this->debug)
|
||||
{
|
||||
error_log(__METHOD__."($path) took ".(microtime(true) - $starttime).
|
||||
' to return '.count($files['files']).' items');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -175,29 +220,29 @@ class calendar_groupdav extends groupdav_handler
|
||||
switch($filter['name'])
|
||||
{
|
||||
case 'comp-filter':
|
||||
if ($this->debug > 1) error_log(__METHOD__."($path,...) comp-filter='{$filter['attrs']['name']}'");
|
||||
if ($this->debug > 1) error_log(__METHOD__."($options[path],...) comp-filter='{$filter['attrs']['name']}'");
|
||||
|
||||
switch($filter['attrs']['name'])
|
||||
{
|
||||
case 'VTODO':
|
||||
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);
|
||||
//$infolog_handler = new groupdav_infolog();
|
||||
//return $infolog_handler->propfind($path,$options,$files,$user,$method);
|
||||
case 'VCALENDAR':
|
||||
case 'VEVENT':
|
||||
break; // that's our default anyway
|
||||
}
|
||||
break;
|
||||
case 'prop-filter':
|
||||
if ($this->debug > 1) error_log(__METHOD__."($path,...) prop-filter='{$filter['attrs']['name']}'");
|
||||
if ($this->debug > 1) error_log(__METHOD__."($options[path],...) prop-filter='{$filter['attrs']['name']}'");
|
||||
$prop_filter = $filter['attrs']['name'];
|
||||
break;
|
||||
case 'text-match':
|
||||
if ($this->debug > 1) error_log(__METHOD__."($path,...) text-match: $prop_filter='{$filter['data']}'");
|
||||
if ($this->debug > 1) error_log(__METHOD__."($options[path],...) text-match: $prop_filter='{$filter['data']}'");
|
||||
if (!isset($this->filter_prop2cal[strtoupper($prop_filter)]))
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."($path,".array2string($options).",,$user) unknown property '$prop_filter' --> ignored");
|
||||
if ($this->debug) error_log(__METHOD__."($options[path],".array2string($options).",...) unknown property '$prop_filter' --> ignored");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -206,15 +251,15 @@ class calendar_groupdav extends groupdav_handler
|
||||
unset($prop_filter);
|
||||
break;
|
||||
case 'param-filter':
|
||||
if ($this->debug) error_log(__METHOD__."($path,...) param-filter='{$filter['attrs']['name']}' not (yet) implemented!");
|
||||
if ($this->debug) error_log(__METHOD__."($options[path],...) param-filter='{$filter['attrs']['name']}' not (yet) implemented!");
|
||||
break;
|
||||
case 'time-range':
|
||||
if ($this->debug > 1) error_log(__FILE__ . __METHOD__."($path,...) time-range={$filter['attrs']['start']}-{$filter['attrs']['end']}");
|
||||
if ($this->debug > 1) error_log(__FILE__ . __METHOD__."($options[path],...) time-range={$filter['attrs']['start']}-{$filter['attrs']['end']}");
|
||||
$cal_filters['start'] = $filter['attrs']['start'];
|
||||
$cal_filters['end'] = $filter['attrs']['end'];
|
||||
break;
|
||||
default:
|
||||
if ($this->debug) error_log(__METHOD__."($path,".array2string($options).",,$user) unknown filter --> ignored");
|
||||
if ($this->debug) error_log(__METHOD__."($options[path],".array2string($options).",...) unknown filter --> ignored");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -262,7 +307,7 @@ class calendar_groupdav extends groupdav_handler
|
||||
$cal_filters['query'][] = 'egw_cal.cal_id IN ('.implode(',',array_map(create_function('$n','return (int)$n;'),$ids)).')';
|
||||
}
|
||||
|
||||
if ($this->debug > 1) error_log(__FILE__ . __METHOD__ ."($path,,,$user,$id) calendar-multiget: ids=".implode(',',$ids));
|
||||
if ($this->debug > 1) error_log(__FILE__ . __METHOD__ ."($options[path],...,$id) calendar-multiget: ids=".implode(',',$ids));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -280,14 +325,103 @@ class calendar_groupdav extends groupdav_handler
|
||||
{
|
||||
return $event;
|
||||
}
|
||||
$handler = $this->_get_handler();
|
||||
$options['data'] = $handler->exportVCal(array($event),'2.0','PUBLISH');
|
||||
$options['data'] = $this->iCal($event);
|
||||
$options['mimetype'] = 'text/calendar; charset=utf-8';
|
||||
header('Content-Encoding: identity');
|
||||
header('ETag: '.$this->get_etag($event));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an iCal for the given event
|
||||
*
|
||||
* Taking into account virtual an real exceptions for recuring events
|
||||
*
|
||||
* @param array $event
|
||||
* @return string
|
||||
*/
|
||||
private function iCal(array $event)
|
||||
{
|
||||
static $handler = null;
|
||||
if (is_null($handler)) $handler = $this->_get_handler();
|
||||
|
||||
$events = array($event);
|
||||
|
||||
// for recuring events we have to add the exceptions
|
||||
if ($this->client_shared_uid_exceptions && $event['recur_type'] && !empty($event['uid']))
|
||||
{
|
||||
$events =& self::get_series($event['uid'],$this->bo);
|
||||
}
|
||||
elseif(!$this->client_shared_uid_exceptions && $event['reference'])
|
||||
{
|
||||
$events[0]['uid'] .= '-'.$event['id']; // force a different uid
|
||||
}
|
||||
return $handler->exportVCal($events,'2.0','PUBLISH');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array with events of a series identified by its UID (master and all exceptions)
|
||||
*
|
||||
* Maybe that should be part of calendar_bo
|
||||
*
|
||||
* @param string $uid UID
|
||||
* @param calendar_bo $bo=null calendar_bo object to reuse for search call
|
||||
* @return array
|
||||
*/
|
||||
private static function &get_series($uid,calendar_bo $bo=null)
|
||||
{
|
||||
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',
|
||||
));
|
||||
$events = array_merge(array($master), $events);
|
||||
foreach($events as $k => &$recurrence)
|
||||
{
|
||||
//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));
|
||||
// remove from masters recur_exception, as exception is include
|
||||
// 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 (($e = array_search($recurrence['reference'],$exceptions)) !== false)
|
||||
{
|
||||
unset($exceptions[$e]);
|
||||
}
|
||||
continue; // nothing to change
|
||||
}
|
||||
// now we need to check if this recurrence is an exception
|
||||
if ($master['participants'] == $recurrence['participants'])
|
||||
{
|
||||
//error_log('NO exception: '.array2string($recurrence));
|
||||
unset($events[$k]); // no exception --> remove it
|
||||
continue;
|
||||
}
|
||||
// this is a virtual exception now (no extra event/cal_id in DB)
|
||||
//error_log('virtual exception: '.array2string($recurrence));
|
||||
$recurrence['reference'] = $recurrence['start'];
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle put request for an event
|
||||
*
|
||||
@ -298,7 +432,7 @@ class calendar_groupdav extends groupdav_handler
|
||||
*/
|
||||
function put(&$options,$id,$user=null)
|
||||
{
|
||||
if($this->debug) error_log(__METHOD__."($id, $user)".print_r($options,true));
|
||||
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);
|
||||
|
||||
@ -308,6 +442,12 @@ class calendar_groupdav extends groupdav_handler
|
||||
return $event;
|
||||
}
|
||||
$handler = $this->_get_handler();
|
||||
|
||||
if (!is_numeric($id) && ($foundEntries = $handler->find_event($options['content'], 'exact')))
|
||||
{
|
||||
$id = array_shift($foundEntries);
|
||||
}
|
||||
|
||||
if (!($cal_id = $handler->importVCal($options['content'],is_numeric($id) ? $id : -1,
|
||||
self::etag2value($this->http_if_match))))
|
||||
{
|
||||
@ -325,11 +465,85 @@ class calendar_groupdav extends groupdav_handler
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix event series with exceptions, called by calendar_ical::importVCal():
|
||||
* a) only series master = first event got cal_id from URL
|
||||
* b) exceptions need to be checked if they are already in DB or new
|
||||
* c) recurrence-id of (real not virtual) exceptions need to be re-added to master
|
||||
*
|
||||
* @param array &$events
|
||||
*/
|
||||
static function fix_series(array &$events)
|
||||
{
|
||||
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 = $exceptions = array();
|
||||
foreach(self::get_series($events[0]['uid'],$bo) as $k => $event)
|
||||
{
|
||||
if (!$k) $master = $event;
|
||||
if ($event['reference'])
|
||||
{
|
||||
$org_recurrences[$event['reference']] = $event;
|
||||
}
|
||||
}
|
||||
|
||||
// assign cal_id's to already existing recurrences and evtl. re-add recur_exception to master
|
||||
foreach($events as $k => &$recurrence)
|
||||
{
|
||||
if (!$recurrence['reference'])
|
||||
{
|
||||
// master
|
||||
$recurrence['id'] = $master['id'];
|
||||
$master =& $events[$k];
|
||||
continue;
|
||||
}
|
||||
|
||||
// from now on we deal with exceptions
|
||||
$org_recurrence = $org_recurrences[$recurrence['reference']];
|
||||
if (isset($org_recurrence)) // already existing recurrence
|
||||
{
|
||||
error_log(__METHOD__.'() setting id #'.$org_recurrence['id']).' for '.$recurrence['reference'].' = '.date('Y-m-d H:i:s',$recurrence['reference']);
|
||||
$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['reference'].' = '.date('Y-m-d H:i:s',$recurrence['reference']));
|
||||
$exceptions[] = $recurrence['reference'];
|
||||
}
|
||||
// remove recurrence to be able to detect deleted exceptions
|
||||
unset($org_recurrences[$recurrence['reference']]);
|
||||
}
|
||||
}
|
||||
$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']);
|
||||
$bo->delete($org_recurrence['id']); // might fail because of permissions
|
||||
}
|
||||
else // virtual recurrence
|
||||
{
|
||||
error_log(__METHOD__.'() ToDO: delete virtual exception '.$org_recurrence['reference'].' = '.date('Y-m-d H:i:s',$org_recurrence['reference']));
|
||||
// todo: reset status and participants to master default
|
||||
}
|
||||
}
|
||||
foreach($events as $n => $event) error_log(__METHOD__." $n after: ".array2string($event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete request for an event
|
||||
*
|
||||
* If current user has no right to delete the event, but is an attendee, we reject the event for him.
|
||||
*
|
||||
* @todo remove (non-virtual) exceptions, if series master gets deleted
|
||||
* @param array &$options
|
||||
* @param int $id
|
||||
* @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
|
||||
@ -372,21 +586,41 @@ class calendar_groupdav extends groupdav_handler
|
||||
*/
|
||||
function get_etag($entry)
|
||||
{
|
||||
$e_in = $entry;
|
||||
if (!is_array($entry))
|
||||
{
|
||||
$entry = $this->read($entry);
|
||||
}
|
||||
if (!$entry['id'] || !isset($entry['etag']) || !isset($entry['participants']))
|
||||
{
|
||||
if ($this->debug > 1) error_log(__FILE__ . __METHOD__."($e_in): id=$entry[id], etag=$entry[etag], isset(participants)=".(int)isset($entry['participants']).", title=$entry[title]: id, etag or participants not set!!!");
|
||||
}
|
||||
$etag = $entry['id'].':'.$entry['etag'];
|
||||
// add a hash over the participants and their stati
|
||||
ksort($entry['participants']); // create a defined order
|
||||
$etag .= ':'.md5(serialize($entry['participants']));
|
||||
//error_log(__FILE__ .__METHOD__ . "($entry[id] ($entry[etag]): $entry[title] --> etag=$etag");
|
||||
return $etag;
|
||||
|
||||
// use new MAX(modification date) of egw_cal_user table (deals with virtual exceptions too)
|
||||
if (isset($entry['max_user_modified']))
|
||||
{
|
||||
$etag .= ':'.$entry['max_user_modified'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$etag .= ':'.$this->bo->so->max_user_modified($entry['id']);
|
||||
}
|
||||
// include exception etags into our own etag, if exceptions are included
|
||||
if ($this->client_shared_uid_exceptions && !empty($entry['uid']) &&
|
||||
$entry['recur_type'] != MCAL_RECUR_NONE && $entry['recur_exception'])
|
||||
{
|
||||
$events =& $this->bo->search(array(
|
||||
'query' => array('cal_uid' => $entry['uid']),
|
||||
'daywise' => false,
|
||||
'enum_recuring' => false,
|
||||
'date_format' => 'server',
|
||||
));
|
||||
foreach($events as $k => &$recurrence)
|
||||
{
|
||||
if ($recurrence['reference']) // ignore series master
|
||||
{
|
||||
$etag .= ':'.substr($this->get_etag($recurrence),1,-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
//error_log(__METHOD__ . "($entry[id] ($entry[etag]): $entry[title] --> etag=$etag");
|
||||
return '"'.$etag.'"';
|
||||
}
|
||||
|
||||
/**
|
||||
|
File diff suppressed because it is too large
Load Diff
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);
|
||||
@ -112,7 +113,7 @@ class calendar_so
|
||||
* All times (start, end and modified) are returned as timesstamps in servertime!
|
||||
*
|
||||
* @param int|array|string $ids id or array of id's of the entries to read, or string with a single uid
|
||||
* @param int $recur_date=0 if set read the next recurrance at or after the timestamp, default 0 = read the initital one
|
||||
* @param int $recur_date=0 if set read the next recurrence at or after the timestamp, default 0 = read the initital one
|
||||
* @return array|boolean array with id => data pairs or false if entry not found
|
||||
*/
|
||||
function read($ids,$recur_date=0)
|
||||
@ -179,6 +180,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
|
||||
@ -231,6 +244,37 @@ class calendar_so
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum modification time of participant data of given event(s)
|
||||
*
|
||||
* This includes ALL recurences of an event series
|
||||
*
|
||||
* @param int|array $ids one or multiple cal_id's
|
||||
* @return int|array (array of) modification timestamp(s)
|
||||
*/
|
||||
function max_user_modified($ids)
|
||||
{
|
||||
$etags = array();
|
||||
if (is_array($ids))
|
||||
{
|
||||
$events = $ids;
|
||||
}
|
||||
else
|
||||
{
|
||||
$events = array($ids);
|
||||
}
|
||||
foreach ($events as $id)
|
||||
{
|
||||
if (!($ts = $GLOBALS['egw']->contenthistory->getTSforAction('calendar', $id, 'modify')))
|
||||
{
|
||||
$ts = $GLOBALS['egw']->contenthistory->getTSforAction('calendar', $id, 'add');
|
||||
}
|
||||
if ($ts) $etags[$id] = $ts;
|
||||
}
|
||||
//echo "<p>".__METHOD__.'('.array2string($ids).') = '.array2string($etags)."</p>\n";
|
||||
return is_array($ids) ? $etags : $etags[$ids];
|
||||
}
|
||||
|
||||
/**
|
||||
* generate SQL to filter after a given category (evtl. incl. subcategories)
|
||||
*
|
||||
@ -285,7 +329,7 @@ class calendar_so
|
||||
*
|
||||
* ToDo: search custom-fields too
|
||||
*/
|
||||
function &search($start,$end,$users,$cat_id=0,$filter='',$query='',$offset=False,$num_rows=0,$order='cal_start',$show_rejected=true,$_cols=null,$append='')
|
||||
function &search($start,$end,$users,$cat_id=0,$filter='',$query='',$offset=False,$num_rows=0,$order='cal_start',$show_rejected=true,$_cols=null,$append='',$cfs=null)
|
||||
{
|
||||
//echo '<p>'.__METHOD__.'('.($start ? date('Y-m-d H:i',$start) : '').','.($end ? date('Y-m-d H:i',$end) : '').','.array2string($users).','.array2string($cat_id).",'$filter',".array2string($query).",$offset,$num_rows,$order,$show_rejected,".array2string($_cols).",$append,".array2string($cfs).")</p>\n";
|
||||
|
||||
@ -326,6 +370,11 @@ class calendar_so
|
||||
'cal_user_type' => $type,
|
||||
'cal_user_id' => $ids,
|
||||
));
|
||||
if ($type == 'u' && $show_rejected)
|
||||
{
|
||||
$cal_table_def = $this->db->get_table_definitions('calendar',$this->cal_table);
|
||||
$to_or[] = $this->db->expression($cal_table_def,array('cal_owner' => $ids));
|
||||
}
|
||||
}
|
||||
$where[] = '('.implode(' OR ',$to_or).')';
|
||||
|
||||
@ -338,7 +387,7 @@ class calendar_so
|
||||
if ($start) $where[] = (int)$start.' < cal_end';
|
||||
if ($end) $where[] = 'cal_start < '.(int)$end;
|
||||
|
||||
if (!preg_match('/^[a-z_ ,]+$/i',$order)) $order = 'cal_start'; // gard against SQL injunktion
|
||||
if (!preg_match('/^[a-z_ ,]+$/i',$order)) $order = 'cal_start'; // gard against SQL injection
|
||||
|
||||
if ($this->db->capabilities['distinct_on_text'] && $this->db->capabilities['union'])
|
||||
{
|
||||
@ -390,12 +439,24 @@ 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();
|
||||
|
||||
@ -523,6 +584,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)");
|
||||
|
||||
@ -545,7 +608,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)
|
||||
{
|
||||
@ -684,7 +759,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']))
|
||||
@ -860,7 +935,7 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
/**
|
||||
* splits the combined status, quantity and role
|
||||
*
|
||||
* @param string &$status I: combined value, O: status letter: U, T, A, R
|
||||
* @param string &$status I: combined value, O: status letter: U, T, A, R, D
|
||||
* @param int &$quantity only O: quantity
|
||||
* @param string &$role only O: role
|
||||
*/
|
||||
@ -875,6 +950,10 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
if ($matches[2]) $role = $matches[2];
|
||||
$status = $status[0];
|
||||
}
|
||||
elseif ($status === true)
|
||||
{
|
||||
$status = 'U';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1009,7 +1088,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')
|
||||
{
|
||||
@ -1323,16 +1403,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 onyl 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 . ')';
|
||||
@ -1347,6 +1425,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',
|
||||
@ -1426,21 +1507,21 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
* irregular participant stati
|
||||
*
|
||||
* @param array $event Recurring Event.
|
||||
* @param int servertime=0 == 0 -> export event with UTC timestamps
|
||||
* != 0 -> export with servertime timestamps
|
||||
* @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 boolean $show_rejected=true should the search return rejected invitations
|
||||
*
|
||||
* @return array Array of exception days (false for non-recurring events).
|
||||
*/
|
||||
function get_recurrence_exceptions(&$event, $servertime=0, $start=0, $end=0)
|
||||
function get_recurrence_exceptions(&$event, $start=0, $end=0, $show_rejected=true)
|
||||
{
|
||||
$cal_id = (int) $event['id'];
|
||||
$user = $GLOBALS['egw_info']['user']['account_id'];
|
||||
if (!$cal_id || $event['recur_type'] == MCAL_RECUR_NONE) return false;
|
||||
|
||||
$days = array();
|
||||
$days = $removed_days = array();
|
||||
|
||||
$participants = $this->get_participants($event['id'], 0);
|
||||
$participants = $this->get_participants($cal_id, 0);
|
||||
|
||||
// Check if the stati for all participants are identical for all recurrences
|
||||
foreach ($participants as $uid => $attendee)
|
||||
@ -1450,13 +1531,18 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
case 'u': // account
|
||||
case 'c': // contact
|
||||
case 'e': // email address
|
||||
$recurrences = $this->get_recurrences($event['id'], $uid, $start, $end);
|
||||
$recurrences = $this->get_recurrences($cal_id, $uid, $start, $end);
|
||||
foreach ($recurrences as $recur_date => $recur_status)
|
||||
{
|
||||
if ($uid == $user && !$show_rejected && $recur_status[0] == 'R')
|
||||
{
|
||||
$removed_days[$recur_date] = $recur_date;
|
||||
continue;
|
||||
}
|
||||
if ($recur_date && $recur_status != $recurrences[0])
|
||||
{
|
||||
// Every distinct status results in an exception
|
||||
$days[] = $recur_date;
|
||||
$days[$recur_date] = $recur_date;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -1464,6 +1550,7 @@ ORDER BY cal_user_type, cal_usre_id
|
||||
break;
|
||||
}
|
||||
}
|
||||
$days = array_diff($days, $removed_days);
|
||||
$days = array_unique($days);
|
||||
sort($days);
|
||||
return $days;
|
||||
|
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 |
@ -1222,7 +1222,7 @@ class Horde_iCalendar {
|
||||
$attr_string = $name . $params_str;
|
||||
if (strlen($value) > 0) {
|
||||
$attr_string .= ':' . $value;
|
||||
} elseif ($name != 'RRULE') {
|
||||
} elseif ($name != 'RRULE' && $name != 'ATTENDEE' && $name != 'ORGANIZER') {
|
||||
$attr_string .= ':';
|
||||
}
|
||||
if (!$this->isOldFormat()) {
|
||||
|
Loading…
Reference in New Issue
Block a user