diff --git a/calendar/inc/class.calendar_bo.inc.php b/calendar/inc/class.calendar_bo.inc.php
index 7cb6f3624d..d29486bb9e 100644
--- a/calendar/inc/class.calendar_bo.inc.php
+++ b/calendar/inc/class.calendar_bo.inc.php
@@ -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;
diff --git a/calendar/inc/class.calendar_boupdate.inc.php b/calendar/inc/class.calendar_boupdate.inc.php
index 3dfe41a76e..2909986aeb 100644
--- a/calendar/inc/class.calendar_boupdate.inc.php
+++ b/calendar/inc/class.calendar_boupdate.inc.php
@@ -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,
+ );
+ }
}
\ No newline at end of file
diff --git a/calendar/inc/class.calendar_groupdav.inc.php b/calendar/inc/class.calendar_groupdav.inc.php
index 964464d7c8..79bf6a7487 100644
--- a/calendar/inc/class.calendar_groupdav.inc.php
+++ b/calendar/inc/class.calendar_groupdav.inc.php
@@ -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.'"';
}
/**
diff --git a/calendar/inc/class.calendar_ical.inc.php b/calendar/inc/class.calendar_ical.inc.php
index fe7da77448..0ead233939 100644
--- a/calendar/inc/class.calendar_ical.inc.php
+++ b/calendar/inc/class.calendar_ical.inc.php
@@ -42,7 +42,7 @@ class calendar_ical extends calendar_boupdate
'A' => 'ACCEPTED',
'R' => 'DECLINED',
'T' => 'TENTATIVE',
- 'D' => 'DELEGATED',
+ 'D' => 'DELEGATED'
);
/**
* @var array conversation of the participant status ical => egw
@@ -75,7 +75,7 @@ class calendar_ical extends calendar_boupdate
5 => 2, // normal
4 => 3, 3 => 3, 2 => 3, 1 => 3, // high
);
-
+
/**
* @var array $priority_egw2funambol conversion of the priority egw => funambol
*/
@@ -210,8 +210,8 @@ class calendar_ical extends calendar_boupdate
'LOCATION' => 'location',
'DTSTART' => 'start',
'DTEND' => 'end',
- 'ORGANIZER' => 'owner',
'ATTENDEE' => 'participants',
+ 'ORGANIZER' => 'owner',
'RRULE' => 'recur_type',
'EXDATE' => 'recur_exception',
'PRIORITY' => 'priority',
@@ -220,6 +220,7 @@ class calendar_ical extends calendar_boupdate
'UID' => 'uid',
'RECURRENCE-ID' => 'reference',
'SEQUENCE' => 'etag',
+ 'STATUS' => 'status',
);
if (!is_array($this->supportedFields)) $this->setSupportedFields();
@@ -238,25 +239,102 @@ class calendar_ical extends calendar_boupdate
if (!is_array($events)) $events = array($events);
- while (($event = array_pop($events)))
+ foreach ($events as $event)
{
+ $mailtoOrganizer = false;
+ $organizerCN = false;
+ $recurrence = $this->date2usertime($recur_date);
+ if (!is_array($event)
+ && !($event = $this->read($event, $recurrence, false, 'server')))
+ {
+ if ($this->read($event, $recurrence, true, 'server'))
+ {
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ '() User does not have the permission to read event ' . $event['id']. "\n",
+ 3,$this->logfile);
+ }
+ return -1; // Permission denied
+ }
+ else
+ {
+ $retval = false; // Entry does not exist
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ "() Event $event not found.\n",
+ 3, $this->logfile);
+ }
+ }
+ continue;
+ }
+ if ($this->isWholeDay($event)) $event['whole_day'] = true;
+
if (strpos($this->productName, 'palmos'))
{
$utc = false;
- $date_format = 'ts';
+
+ if (isset($event['whole_day']))
+ {
+ if (isset($event['reference']))
+ {
+ $event['reference'] = mktime(0, 0, 0,
+ date('m', $event['reference']),
+ date('d', $event['reference']),
+ date('Y', $event['reference'])
+ );
+ }
+
+ foreach((array)$event['recur_exception'] as $n => $date)
+ {
+ $event['recur_exception'][$n] = mktime(0, 0, 0,
+ date('m', $date),
+ date('d', $date),
+ date('Y', $date)
+ );
+ }
+ if (isset($event['alarm']) && is_array($event['alarm']))
+ {
+ foreach($event['alarm'] as $n => $alarm)
+ {
+ $event['alarm'][$n]['time'] = $this->date2usertime($alarm['time']);
+ }
+ }
+ }
+ else
+ {
+ $new_events = array($event);
+ $this->db2data($new_events, 'ts');
+ $event = array_shift($new_events);
+ }
}
else
{
$utc = true;
- $date_format = 'server';
}
- if (!is_array($event)
- && !($event = $this->read($event, $recur_date, false, $date_format)))
+
+ if ($this->log)
{
- return false; // no permission to read $cal_id
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ '(' . $event['id']. ',' . $recurrence . ")\n" .
+ array2string($event)."\n",3,$this->logfile);
}
- if ($recur_date)
+
+ if ($recurrence)
{
+ if (!isset($this->supportedFields['participants']))
+ {
+ // We don't need status only exceptions
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ "($_id, $recurrence) Gratuitous pseudo exception, skipped ...\n",
+ 3,$this->logfile);
+ }
+ continue; // unsupported status only exception
+ }
+
// force single event
foreach (array('recur_enddate','recur_interval','recur_exception','recur_data','recur_date','id','etag') as $name)
{
@@ -266,18 +344,13 @@ class calendar_ical extends calendar_boupdate
}
elseif ($event['recur_enddate'])
{
- $delta = $event['end'] - mktime(0, 0, 0,
- date('m', $event['start']),
- date('d', $event['start']),
- date('Y', $event['start'])
+ $event['recur_enddate'] = mktime(23, 59, 59,
+ date('m', $event['recur_enddate']),
+ date('d', $event['recur_enddate']),
+ date('Y', $event['recur_enddate'])
);
- if ($delta == 0) $delta = 24 * 60 * 60;
-
- $event['recur_enddate'] += $delta;
}
- if ($this->log) error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n".array2string($event)."\n",3,$this->logfile);
-
if (!$serverTZ && date('e', $event['start']) != 'UTC'
&& ($event['recur_type'] != MCAL_RECUR_NONE
|| $this->useServerTZ))
@@ -327,6 +400,28 @@ class calendar_ical extends calendar_boupdate
}
}
+ if ($event['recur_type'] != MCAL_RECUR_NONE)
+ {
+ $exceptions = array();
+
+ // dont use "virtual" exceptions created by participant status for GroupDAV or file export
+ if (!in_array($this->productManufacturer,array('file','groupdav')))
+ {
+ $exceptions = $this->so->get_recurrence_exceptions($event);
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."(PSEUDO EXCEPTIONS)\n" .
+ array2string($exceptions)."\n",3,$this->logfile);
+ }
+ }
+ if (is_array($event['recur_exception']))
+ {
+ $exceptions = array_unique(array_merge($exceptions, $event['recur_exception']));
+ sort($exceptions);
+ }
+ $event['recur_exception'] = $exceptions;
+ }
+
foreach ($egwSupportedFields as $icalFieldName => $egwFieldName)
{
if (!isset($this->supportedFields[$egwFieldName])) continue;
@@ -339,11 +434,17 @@ class calendar_ical extends calendar_boupdate
foreach ((array)$event['participants'] as $uid => $status)
{
if (!($info = $this->resource_info($uid))) continue;
- if ($uid == $event['owner'] && $status == 'A') continue; // Organizer
+ $mailtoParticipant = $info['email'] ? 'MAILTO:'.$info['email'] : '';
+ $participantCN = '"' . ($info['cn'] ? $info['cn'] : $info['name']) . '"';
+ calendar_so::split_status($status, $quantity, $role);
+ if ($role == 'CHAIR' && $uid != $this->user)
+ {
+ $mailtoOrganizer = $mailtoParticipant;
+ $organizerCN = $participantCN;
+ if ($status == 'U') continue; // saved ORGANIZER
+ }
// RB: MAILTO href contains only the email-address, NO cn!
- $attributes['ATTENDEE'][] = $info['email'] ? 'MAILTO:'.$info['email'] : '';
- // ROLE={CHAIR|REQ-PARTICIPANT|OPT-PARTICIPANT|NON-PARTICIPANT|X-*}
- calendar_so::split_status($status,$quantity,$role);
+ $attributes['ATTENDEE'][] = $mailtoParticipant;
// RSVP={TRUE|FALSE} // resonse expected, not set in eGW => status=U
$rsvp = $status == 'U' ? 'TRUE' : 'FALSE';
// PARTSTAT={NEEDS-ACTION|ACCEPTED|DECLINED|TENTATIVE|DELEGATED|COMPLETED|IN-PROGRESS} everything from delegated is NOT used by eGW atm.
@@ -366,8 +467,9 @@ class calendar_ical extends calendar_boupdate
$cutype = 'UNKNOWN';
break;
};
+ // ROLE={CHAIR|REQ-PARTICIPANT|OPT-PARTICIPANT|NON-PARTICIPANT|X-*}
$parameters['ATTENDEE'][] = array(
- 'CN' => '"'.($info['cn'] ? $info['cn'] : $info['name']).'"',
+ 'CN' => $participantCN,
'ROLE' => $role,
'PARTSTAT' => $status,
'CUTYPE' => $cutype,
@@ -378,34 +480,52 @@ class calendar_ical extends calendar_boupdate
break;
case 'CLASS':
- $attributes['CLASS'] = $event['public'] ? 'PUBLIC' : 'CONFIDENTIAL';
+ if ($this->productManufacturer == 'funambol')
+ {
+ $attributes['CLASS'] = $event['public'] ? 'PUBLIC' : 'PRIVATE';
+ }
+ else
+ {
+ $attributes['CLASS'] = $event['public'] ? 'PUBLIC' : 'CONFIDENTIAL';
+ }
break;
- case 'ORGANIZER': // according to iCalendar standard, ORGANIZER not used for events in the own calendar
- if ($event['owner'] != $this->user || $this->productManufacturer != 'groupdav')
+ case 'ORGANIZER':
+ // according to iCalendar standard, ORGANIZER not used for events in the own calendar
+ if (!$organizerCN &&
+ ($event['owner'] != $this->user
+ || $this->productManufacturer != 'groupdav'))
{
$mailtoOrganizer = $GLOBALS['egw']->accounts->id2name($event['owner'],'account_email');
- $attributes['ORGANIZER'] = $mailtoOrganizer ? 'MAILTO:'.$mailtoOrganizer : '';
- $parameters['ORGANIZER']['CN'] = '"'.trim($GLOBALS['egw']->accounts->id2name($event['owner'],'account_firstname').' '.
- $GLOBALS['egw']->accounts->id2name($event['owner'],'account_lastname')).'"';
+ $mailtoOrganizer = $mailtoOrganizer ? 'MAILTO:'.$mailtoOrganizer : '';
+ $organizerCN = '"' . trim($GLOBALS['egw']->accounts->id2name($event['owner'],'account_firstname')
+ . ' ' . $GLOBALS['egw']->accounts->id2name($event['owner'],'account_lastname')) . '"';
+ }
+ if ($organizerCN)
+ {
+ $attributes['ORGANIZER'] = $mailtoOrganizer;
+ $parameters['ORGANIZER']['CN'] = $organizerCN;
}
break;
case 'DTSTART':
- if ($utc)
+ if (!isset($event['whole_day']))
{
- $attributes['DTSTART'] = $event['start'];
- }
- else
- {
- $attributes['DTSTART'] = date('Ymd\THis', $event['start']);
- if ($serverTZ) $parameters['DTSTART']['TZID'] = $serverTZ;
+ if ($utc)
+ {
+ $attributes['DTSTART'] = $event['start'];
+ }
+ else
+ {
+ $attributes['DTSTART'] = date('Ymd\THis', $event['start']);
+ if ($serverTZ) $parameters['DTSTART']['TZID'] = $serverTZ;
+ }
}
break;
case 'DTEND':
// write start + end of whole day events as dates
- if ($this->isWholeDay($event))
+ if (isset($event['whole_day']))
{
$event['end-nextday'] = $event['end'] + 12*3600; // we need the date of the next day, as DTEND is non-inclusive (= exclusive) in rfc2445
foreach (array('start' => 'DTSTART','end-nextday' => 'DTEND') as $f => $t)
@@ -507,22 +627,11 @@ class calendar_ical extends calendar_boupdate
case 'EXDATE':
if ($event['recur_type'] == MCAL_RECUR_NONE) break;
- $days = array();
- // dont use "virtual" exceptions created by participant status for GroupDAV or file export
- if (!in_array($this->productManufacturer,array('file','groupdav')))
- {
- $days = $this->so->get_recurrence_exceptions($event);
- }
- if (is_array($event['recur_exception']))
- {
- $days = array_merge($days,$event['recur_exception']); // can NOT use +, as it overwrites numeric indexes
- }
+ $days = $event['recur_exception'];
if (!empty($days))
{
- $days = array_unique($days);
- sort($days);
// use 'DATE' instead of 'DATE-TIME' on whole day events
- if ($this->isWholeDay($event))
+ if (isset($event['whole_day']))
{
$value_type = 'DATE';
foreach ($days as $id => $timestamp)
@@ -555,7 +664,9 @@ class calendar_ical extends calendar_boupdate
break;
case 'PRIORITY':
- if($this->productManufacturer == 'funambol')
+ if ($this->productManufacturer == 'funambol' &&
+ (strpos($this->productName, 'outlook') !== false
+ || strpos($this->productName, 'pocket pc') !== false))
{
$attributes['PRIORITY'] = (int) $this->priority_egw2funambol[$event['priority']];
}
@@ -576,11 +687,21 @@ class calendar_ical extends calendar_boupdate
}
break;
+ case 'STATUS':
+ $attributes['STATUS'] = 'CONFIRMED';
+ break;
+
case 'CATEGORIES':
- if ($event['category'])
+ if ($event['category'] && ($values['CATEGORIES'] = $this->get_categories($event['category'])))
{
- $attributes['CATEGORIES'] = '';
- $values['CATEGORIES'] = $this->get_categories($event['category']);
+ if (count($values['CATEGORIES']) == 1)
+ {
+ $attributes['CATEGORIES'] = array_shift($values['CATEGORIES']);
+ }
+ else
+ {
+ $attributes['CATEGORIES'] = '';
+ }
}
break;
@@ -592,7 +713,7 @@ class calendar_ical extends calendar_boupdate
if ($recur_date)
{
// We handle a status only exception
- if ($this->isWholeDay($event))
+ if (isset($event['whole_day']))
{
$arr = $this->date2array($recur_date);
$vevent->setAttribute($icalFieldName, array(
@@ -617,7 +738,7 @@ class calendar_ical extends calendar_boupdate
}
elseif ($event['reference'])
{
- if ($this->isWholeDay($event))
+ if (isset($event['whole_day']))
{
$arr = $this->date2array($event['reference']);
$vevent->setAttribute($icalFieldName, array(
@@ -647,8 +768,14 @@ class calendar_ical extends calendar_boupdate
{
$size = $this->clientProperties[$icalFieldName]['Size'];
$noTruncate = $this->clientProperties[$icalFieldName]['NoTruncate'];
- #Horde::logMessage("vCalendar $icalFieldName Size: $size, NoTruncate: " .
- # ($noTruncate ? 'TRUE' : 'FALSE'), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ if ($this->log && $size > 0)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
+ "() $icalFieldName Size: $size, NoTruncate: " .
+ ($noTruncate ? 'TRUE' : 'FALSE') . "\n",3,$this->logfile);
+ }
+ //Horde::logMessage("vCalendar $icalFieldName Size: $size, NoTruncate: " .
+ // ($noTruncate ? 'TRUE' : 'FALSE'), __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
else
{
@@ -661,20 +788,29 @@ class calendar_ical extends calendar_boupdate
{
if ($noTruncate)
{
- Horde::logMessage("vCalendar $icalFieldName omitted due to maximum size $size",
- __FILE__, __LINE__, PEAR_LOG_WARNING);
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
+ "() $icalFieldName omitted due to maximum size $size\n",3,$this->logfile);
+ }
+ //Horde::logMessage("vCalendar $icalFieldName omitted due to maximum size $size",
+ // __FILE__, __LINE__, PEAR_LOG_WARNING);
continue; // skip field
}
// truncate the value to size
$value = substr($value, 0, $size - 1);
- Horde::logMessage("vCalendar $icalFieldName truncated to maximum size $size",
- __FILE__, __LINE__, PEAR_LOG_INFO);
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
+ "() $icalFieldName truncated to maximum size $size\n",3,$this->logfile);
+ }
+ //Horde::logMessage("vCalendar $icalFieldName truncated to maximum size $size",
+ // __FILE__, __LINE__, PEAR_LOG_INFO);
}
if (!empty($value) || ($size >= 0 && !$noTruncate))
{
$attributes[$icalFieldName] = $value;
}
- break;
}
}
@@ -710,14 +846,34 @@ class calendar_ical extends calendar_boupdate
$attributes['DTSTAMP'] = time();
foreach ($event['alarm'] as $alarmID => $alarmData)
{
+ // skip over alarms that don't have the minimum required info
+ if (!$alarmData['offset'] && !$alarmData['time']) continue;
+
// skip alarms not being set for all users and alarms owned by other users
if ($alarmData['all'] != true && $alarmData['owner'] != $this->user)
{
continue;
}
+ if ($alarmData['offset'])
+ {
+ $alarmData['time'] = $event['start'] - $alarmData['offset'];
+ }
+
+ $description = trim(preg_replace("/\r?\n?\\[[A-Z_]+:.*\\]/i", '', $event['description']));
+
if ($version == '1.0')
{
+ if ($event['title']) $description = $event['title'];
+ if ($description)
+ {
+ $values['DALARM']['snooze_time'] = '';
+ $values['DALARM']['repeat count'] = '';
+ $values['DALARM']['display text'] = $description;
+ $values['AALARM']['snooze_time'] = '';
+ $values['AALARM']['repeat count'] = '';
+ $values['AALARM']['display text'] = $description;
+ }
if ($utc)
{
$attributes['DALARM'] = $alarmData['time'];
@@ -737,15 +893,10 @@ class calendar_ical extends calendar_boupdate
{
// VCalendar 2.0 / RFC 2445
- $description = trim(preg_replace("/\r?\n?\\[[A-Z_]+:.*\\]/i", '', $event['description']));
-
- // skip over alarms that don't have the minimum required info
- if (!$alarmData['offset'] && !$alarmData['time']) continue;
-
// RFC requires DESCRIPTION for DISPLAY
if (!$event['title'] && !$description) continue;
- if ($this->isWholeDay($event) && $alarmData['offset'])
+ if (isset($event['whole_day']) && $alarmData['offset'])
{
$alarmData['time'] = $event['start'] - $alarmData['offset'];
$alarmData['offset'] = false;
@@ -780,7 +931,7 @@ class calendar_ical extends calendar_boupdate
foreach ($attributes as $key => $value)
{
- foreach (is_array($value)&&$parameters[$key]['VALUE']!='DATE' ? $value : array($value) as $valueID => $valueData)
+ foreach (is_array($value) && $parameters[$key]['VALUE']!='DATE' ? $value : array($value) as $valueID => $valueData)
{
$valueData = $GLOBALS['egw']->translation->convert($valueData,$GLOBALS['egw']->translation->charset(),'UTF-8');
$paramData = (array) $GLOBALS['egw']->translation->convert(is_array($value) ?
@@ -788,29 +939,60 @@ class calendar_ical extends calendar_boupdate
$GLOBALS['egw']->translation->charset(),'UTF-8');
$valuesData = (array) $GLOBALS['egw']->translation->convert($values[$key],
$GLOBALS['egw']->translation->charset(),'UTF-8');
- //echo "$key:$valueID: value=$valueData, param=".print_r($paramDate,true)."\n";
- $vevent->setAttribute($key, $valueData, $paramData, true, $valuesData);
- $options = array();
- if ($paramData['CN']) $valueData .= $paramData['CN']; // attendees or organizer CN can contain utf-8 content
- /*if($key != 'RRULE' && preg_match('/([\000-\012\015\016\020-\037\075])/',$valueData)) {
- $options['ENCODING'] = 'QUOTED-PRINTABLE';
- }*/
- if ($this->productManufacturer != 'groupdav' && preg_match('/([\177-\377])/', $valueData))
+ $content = $valueData . implode(';', $valuesData);
+
+ if (preg_match('/[^\x20-\x7F]/', $content) ||
+ ($paramData['CN'] && preg_match('/[^\x20-\x7F]/', $paramData['CN'])))
{
- $options['CHARSET'] = 'UTF-8';
+ $paramData['CHARSET'] = 'UTF-8';
+ switch ($this->productManufacturer)
+ {
+ case 'groupdav':
+ if ($this->productName == 'kde')
+ {
+ $paramData['ENCODING'] = 'QUOTED-PRINTABLE';
+ }
+ else
+ {
+ $paramData['CHARSET'] = '';
+ if (preg_match('/([\000-\012\015\016\020-\037\075])/', $valueData))
+ {
+ $paramData['ENCODING'] = 'QUOTED-PRINTABLE';
+ }
+ else
+ {
+ $paramData['ENCODING'] = '';
+ }
+ }
+ break;
+ case 'funambol':
+ $paramData['ENCODING'] = 'FUNAMBOL-QP';
+ }
}
+ /*
if (preg_match('/([\000-\012])/', $valueData))
{
- if ($this->log) error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."() Has invalid XML data: $valueData",3,$this->logfile);
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
+ "() Has invalid XML data: $valueData",3,$this->logfile);
+ }
}
- $vevent->setParameter($key, $options);
+ */
+ $vevent->setAttribute($key, $valueData, $paramData, true, $valuesData);
}
}
$vcal->addComponent($vevent);
}
$retval = $vcal->exportvCalendar();
- if ($this->log) error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n".array2string($retval)."\n",3,$this->logfile);
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
+ "() '$this->productManufacturer','$this->productName'\n",3,$this->logfile);
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
+ "()\n".array2string($retval)."\n",3,$this->logfile);
+ }
return $retval;
}
@@ -828,25 +1010,168 @@ class calendar_ical extends calendar_boupdate
*/
function importVCal($_vcalData, $cal_id=-1, $etag=null, $merge=false, $recur_date=0)
{
- if ($this->log) error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n".array2string($_vcalData)."\n",3,$this->logfile);
+ if (!is_array($this->supportedFields)) $this->setSupportedFields();
- if (!($events = $this->icaltoegw($_vcalData,$cal_id,$etag,$recur_date)))
+ if (!($events = $this->icaltoegw($_vcalData)))
{
return false;
}
- if (!is_array($this->supportedFields)) $this->setSupportedFields();
+ if ($cal_id > 0)
+ {
+ if (count($events) == 1)
+ {
+ $events[0]['id'] = $cal_id;
+ if (!is_null($etag)) $events[0]['etag'] = (int) $etag;
+ if ($recur_date) $events[0]['reference'] = $recur_date;
+ }
+ elseif (($foundEvent = $this->find_event(array('id' => $cal_id), 'exact')) &&
+ ($eventId = array_shift($foundEvent)) &&
+ ($egwEvent = $this->read($eventId)))
+ {
+ foreach ($events as $k => $event)
+ {
+ if (!isset($event['uid'])) $events[$k]['uid'] = $egwEvent['uid'];
+ }
+ }
+ }
+ // check if we are importing an event series with exceptions in CalDAV
+ // only first event / series master get's cal_id from URL
+ // other events are exceptions and need to be checked if they are new
+ // and for real (not status only) exceptions their recurrence-id need
+ // to be included as recur_exception to the master
+ if ($this->productManufacturer == 'groupdav' && $cal_id > 0 &&
+ count($events) > 1 && !$events[1]['id'] &&
+ $events[0]['recur_type'] != MCAL_RECUR_NONE)
+ {
+ calendar_groupdav::fix_series($events);
+ }
foreach ($events as $event)
{
+ if ($this->isWholeDay($event, true)) $event['whole_day'] = true;
+ if (is_array($event['category']))
+ {
+ $event['category'] = $this->find_or_add_categories($event['category'],
+ isset($event['id']) ? $event['id'] : -1);
+ }
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
+ array2string($event)."\n",3,$this->logfile);
+ }
$updated_id = false;
$event_info = $this->get_event_info($event);
- // common adjustments for new events
- if (!is_array($event_info['stored_event']))
+ // common adjustments for existing events
+ if (is_array($event_info['stored_event']))
{
+ if (empty($event['uid']))
+ {
+ $event['uid'] = $event_info['stored_event']['uid']; // restore the UID if it was not delivered
+ }
+ elseif (empty($event['id']))
+ {
+ $event['id'] = $event_info['stored_event']['id']; // CalDAV does only provide UIDs
+ }
+ if ($merge)
+ {
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ "()[MERGE]\n",3,$this->logfile);
+ }
+
+ // overwrite with server data for merge
+ foreach ($event_info['stored_event'] as $key => $value)
+ {
+ switch ($key)
+ {
+ case 'participants_types':
+ continue;
+
+ case 'participants':
+ foreach ($event_info['stored_event']['participants'] as $uid => $status)
+ {
+ // Is a participant and no longer present in the event?
+ if (!isset($event['participants'][$uid]))
+ {
+ // Add it back in
+ $event['participants'][$uid] = $event['participant_types']['r'][substr($uid,1)] = $status;
+ }
+ }
+ break;
+
+ default:
+ if (!empty($value)) $event[$key] = $value;
+ }
+ }
+ }
+ else
+ {
+ // no merge
+ if (!isset($this->supportedFields['participants']) || !count($event['participants']))
+ {
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ "() No participants\n",3,$this->logfile);
+ }
+
+ // If this is an updated meeting, and the client doesn't support
+ // participants OR the event no longer contains participants, add them back
+ unset($event['participants']);
+ unset($event['participant_types']);
+ }
+ else
+ {
+ // if the client does not return a status, we restore the original one
+ foreach ($event['participants'] as $uid => $status)
+ {
+ // Is it a resource and no longer present in the event?
+ if ($status[0] == 'X')
+ {
+ if (isset($event_info['stored_event']['participants'][$uid]))
+ {
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ "() Restore status for $uid\n",3,$this->logfile);
+ }
+ $event['participants'][$uid] = $event_info['stored_event']['participants'][$uid];
+ }
+ else
+ {
+ $event['participants'][$uid] = calendar_so::combine_status('U');
+ }
+ }
+ }
+ }
+
+ foreach ($event_info['stored_event']['participants'] as $uid => $status)
+ {
+ // Is it a resource and no longer present in the event?
+ if ($uid[0] == 'r' && !isset($event['participants'][$uid]))
+ {
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ "() Restore resource $uid to status $status\n",3,$this->logfile);
+ }
+ // Add it back in
+ $event['participants'][$uid] = $event['participant_types']['r'][substr($uid,1)] = $status;
+ }
+ }
+
+ // avoid that iCal changes the organizer, which is not allowed
+ $event['owner'] = $event_info['stored_event']['owner'];
+ }
+ }
+ else // common adjustments for new events
+ {
+ unset($event['id']);
// set non blocking all day depending on the user setting
- if ($this->isWholeDay($event) && $this->nonBlockingAllday)
+ if (isset($event['whole_day']) && $this->nonBlockingAllday)
{
$event['non_blocking'] = 1;
}
@@ -859,69 +1184,29 @@ class calendar_ical extends calendar_boupdate
$event['owner'] = $this->user;
}
- // add ourself to new events as participant
- if (!isset($this->supportedFields['participants'])
- ||!isset($event['participants'][$this->user]))
- {
- $event['participants'][$this->user] = 'A';
- }
- }
-
- // common adjustments for existing events
- if (is_array($event_info['stored_event']))
- {
- if ($merge)
+ if (!is_array($event['participants']) || !count($event['participants']))
{
- // overwrite with server data for merge
- foreach ($event_info['stored_event'] as $key => $value)
- {
- switch ($key)
- {
- case 'participants_types':
- continue;
-
- case 'participants':
- foreach ($event_info['stored_event']['participants'] as $uid => $status)
- {
- // Is a participant and no longer present in the event?
- if (!isset($event['participants'][$uid]))
- {
- // Add it back in
- $event['participants'][$uid] = $event['participant_types']['r'][substr($uid,1)] = $status;
- }
- }
- break;
-
- default:
- if (!empty($value))
- {
- $event[$key] = $value;
- }
- }
- }
+ $status = $event['owner'] == $this->user ? 'A' : 'U';
+ $status = calendar_so::combine_status($status, 1, 'CHAIR');
+ $event['participants'] = array($event['owner'] => $status);
}
else
{
- // no merge
- if (!isset($this->supportedFields['participants']) || !count($event['participants']))
- {
- // If this is an updated meeting, and the client doesn't support
- // participants OR the event no longer contains participants, add them back
- $event['participants'] = $event_info['stored_event']['participants'];
- $event['participant_types'] = $event_info['stored_event']['participant_types'];
- }
-
- foreach ($event_info['stored_event']['participants'] as $uid => $status)
+ foreach ($event['participants'] as $uid => $status)
{
// Is it a resource and no longer present in the event?
- if ( $uid[0] == 'r' && !isset($event['participants'][$uid]) )
+ if ($status[0] == 'X')
{
- // Add it back in
- $event['participants'][$uid] = $event['participant_types']['r'][substr($uid,1)] = $status;
+ if ($uid == $event['owner'])
+ {
+ $event['participants']['uid'] = calendar_so::combine_status('A', 1, 'CHAIR');
+ }
+ else
+ {
+ $event['participants']['uid'] = calendar_so::combine_status('U');
+ }
}
}
- // avoid that iCal changes the organizer, which is not allowed
- $event['owner'] = $event_info['stored_event']['owner'];
}
}
@@ -967,7 +1252,7 @@ class calendar_ical extends calendar_boupdate
}
break;
- case 'SERIES-EXCEPTION-STATUS':
+ case 'SERIES-PSEUDO-EXCEPTION':
// nothing to do here
break;
}
@@ -989,27 +1274,45 @@ class calendar_ical extends calendar_boupdate
switch ($event_info['type'])
{
case 'SINGLE':
- Horde::logMessage('importVCAL event SINGLE',__FILE__, __LINE__, PEAR_LOG_DEBUG);
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ "(): event SINGLE\n",3,$this->logfile);
+ }
// update the event
if ($event_info['acl_edit'])
{
- $event_to_store = $event; // prevent $event from being changed by the update method
+ // Force SINGLE
+ $event['reference'] = 0;
+ $event_to_store = array($event); // prevent $event from being changed by the update method
+ $this->db2data($event_to_store);
+ $event_to_store = array_shift($event_to_store);
$updated_id = $this->update($event_to_store, true);
unset($event_to_store);
}
break;
case 'SERIES-MASTER':
- Horde::logMessage('importVCAL event SERIES-MASTER',__FILE__, __LINE__, PEAR_LOG_DEBUG);
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ "(): event SERIES-MASTER\n",3,$this->logfile);
+ }
- // remove all known "status only" exceptions and update the event
+ // remove all known pseudo exceptions and update the event
if ($event_info['acl_edit'])
{
- $days = $this->so->get_recurrence_exceptions($event);
+ $days = $this->so->get_recurrence_exceptions($event_info['stored_event']);
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."(PSEUDO EXCEPTIONS):\n" .
+ array2string($days)."\n",3,$this->logfile);
+ }
if (is_array($days))
{
$recur_exceptions = array();
+
foreach ($event['recur_exception'] as $recur_exception)
{
if (!in_array($recur_exception, $days))
@@ -1020,7 +1323,9 @@ class calendar_ical extends calendar_boupdate
$event['recur_exception'] = $recur_exceptions;
}
- $event_to_store = $event; // prevent $event from being changed by the update method
+ $event_to_store = array($event); // prevent $event from being changed by the update method
+ $this->db2data($event_to_store);
+ $event_to_store = array_shift($event_to_store);
$updated_id = $this->update($event_to_store, true);
unset($event_to_store);
}
@@ -1028,7 +1333,11 @@ class calendar_ical extends calendar_boupdate
case 'SERIES-EXCEPTION':
case 'SERIES-EXCEPTION-PROPAGATE':
- Horde::logMessage('importVCAL event SERIES-EXCEPTION',__FILE__, __LINE__, PEAR_LOG_DEBUG);
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ "(): event SERIES-EXCEPTION\n",3,$this->logfile);
+ }
// update event
if ($event_info['acl_edit'])
@@ -1043,23 +1352,36 @@ class calendar_ical extends calendar_boupdate
{
// We create a new exception
unset($event['id']);
- $event_info['master_event']['recur_exception'] = array_unique(array_merge($event_info['master_event']['recur_exception'], array($event['reference'])));
- $event_to_store = $event_info['master_event']; // prevent the master_event from being changed by the update method
- $this->update($event_to_store, true);
- unset($event_to_store);
- $event['reference'] = $event_info['master_event']['id'];
+ unset($event_info['stored_event']);
+ $event['recur_type'] = MCAL_RECUR_NONE;
+ $event_info['master_event']['recur_exception'] =
+ array_unique(array_merge($event_info['master_event']['recur_exception'],
+ array($event['reference'])));
$event['category'] = $event_info['master_event']['category'];
$event['owner'] = $event_info['master_event']['owner'];
+ $event_to_store = array($event_info['master_event']); // prevent the master_event from being changed by the update method
+ $this->db2data($event_to_store);
+ $event_to_store = array_shift($event_to_store);
+ $this->update($event_to_store, true);
+ unset($event_to_store);
}
- $event_to_store = $event; // prevent $event from being changed by update method
- $updated_id = $this->update($event_to_store, true, true, false, false);
+ $event_to_store = array($event); // prevent $event from being changed by update method
+ $this->db2data($event_to_store);
+ $event_to_store = array_shift($event_to_store);
+ $updated_id = $this->update($event_to_store, true);
unset($event_to_store);
}
break;
- case 'SERIES-EXCEPTION-STATUS':
- Horde::logMessage('importVCAL event SERIES-EXCEPTION-STATUS',__FILE__, __LINE__, PEAR_LOG_DEBUG);
+ case 'SERIES-PSEUDO-EXCEPTION':
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ "(): event SERIES-PSEUDO-EXCEPTION\n",3,$this->logfile);
+ }
+ //Horde::logMessage('importVCAL event SERIES-PSEUDO-EXCEPTION',
+ // __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($event_info['acl_edit'])
{
@@ -1075,18 +1397,19 @@ class calendar_ical extends calendar_boupdate
$event_info['master_event']['recur_exception'] = $recur_exceptions;
// save the series master with the adjusted exceptions
- $event_to_store = $event_info['master_event']; // prevent the master_event from being changed by the update method
+ $event_to_store = array($event_info['master_event']); // prevent the master_event from being changed by the update method
+ $this->db2data($event_to_store);
+ $event_to_store = array_shift($event_to_store);
$updated_id = $this->update($event_to_store, true, true, false, false);
unset($event_to_store);
}
-
break;
}
// read stored event into info array for fresh stored (new) events
if (!is_array($event_info['stored_event']) && $updated_id > 0)
{
- $event_info['stored_event'] = $this->read($updated_id);
+ $event_info['stored_event'] = $this->read($updated_id, 0, false, 'server');
}
// update status depending on the given event type
@@ -1112,19 +1435,20 @@ class calendar_ical extends calendar_boupdate
}
break;
- case 'SERIES-EXCEPTION-STATUS':
+ case 'SERIES-PSEUDO-EXCEPTION':
if (is_array($event_info['master_event'])) // status update requires a stored master event
{
+ $recurrence = $this->date2usertime($event['reference']);
if ($event_info['acl_edit'])
{
// update all participants if we have the right to do that
- $this->update_status($event, $event_info['master_event'], $event['reference']);
+ $this->update_status($event, $event_info['stored_event'], $recurrence);
}
elseif (isset($event['participants'][$this->user]) || isset($event_info['master_event']['participants'][$this->user]))
{
// update the users status only
$this->set_status($event_info['master_event']['id'], $this->user,
- ($event['participants'][$this->user] ? $event['participants'][$this->user] : 'R'), $event['reference'], true);
+ ($event['participants'][$this->user] ? $event['participants'][$this->user] : 'R'), $recurrence, true);
}
}
break;
@@ -1139,7 +1463,7 @@ class calendar_ical extends calendar_boupdate
$return_id = is_array($event_info['stored_event']) ? $event_info['stored_event']['id'] : false;
break;
- case 'SERIES-EXCEPTION-STATUS':
+ case 'SERIES-PSEUDO-EXCEPTION':
$return_id = is_array($event_info['master_event']) ? $event_info['master_event']['id'] . ':' . $event['reference'] : false;
break;
@@ -1152,7 +1476,7 @@ class calendar_ical extends calendar_boupdate
else
{
// we did not have sufficient rights to propagate the status only exception to a real one
- // we have to keep the SERIES-EXCEPTION-STATUS id and keep the event untouched
+ // we have to keep the SERIES-PSEUDO-EXCEPTION id and keep the event untouched
$return_id = $event_info['master_event']['id'] . ':' . $event['reference'];
}
break;
@@ -1160,11 +1484,11 @@ class calendar_ical extends calendar_boupdate
if ($this->log)
{
- $egw_event = $this->read($event['id']);
- error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n".array2string($egw_event)."\n",3,$this->logfile);
+ $event_info['stored_event'] = $this->read($event_info['stored_event']['id']);
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()[$updated_id]\n" .
+ array2string($event_info['stored_event'])."\n",3,$this->logfile);
}
}
-
return $return_id;
}
@@ -1239,13 +1563,13 @@ class calendar_ical extends calendar_boupdate
function setSupportedFields($_productManufacturer='', $_productName='')
{
- $state = &$_SESSION['SyncML.state'];
+ $state =& $_SESSION['SyncML.state'];
if (isset($state))
{
$deviceInfo = $state->getClientDeviceInfo();
}
- // store product manufacturer and name, to be able to use it elsewhere
+ // store product manufacturer and name for further usage
if ($_productManufacturer)
{
$this->productManufacturer = strtolower($_productManufacturer);
@@ -1272,6 +1596,11 @@ class calendar_ical extends calendar_boupdate
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['calendar_owner']))
{
$owner = $GLOBALS['egw_info']['user']['preferences']['syncml']['calendar_owner'];
+ if ($owner == 0)
+ {
+ $owner = $GLOBALS['egw_info']['user']['account_primary_group'];
+ }
+
if (0 < (int)$owner && $this->check_perms(EGW_ACL_EDIT,0,$owner))
{
$this->calendarOwner = $owner;
@@ -1289,8 +1618,19 @@ class calendar_ical extends calendar_boupdate
}
}
- Horde::logMessage('setSupportedFields(' . $this->productManufacturer
- . ', ' . $this->productName .')', __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ '(' . $this->productManufacturer .
+ ', '. $this->productName .', ' .
+ ($this->useServerTZ ? 'TRUE' : 'FALSE') .
+ ")\n" , 3, $this->logfile);
+ }
+
+ //Horde::logMessage('setSupportedFields(' . $this->productManufacturer . ', '
+ // . $this->productName .', ' .
+ // ($this->useServerTZ ? 'TRUE' : 'FALSE') .')',
+ // __FILE__, __LINE__, PEAR_LOG_DEBUG);
$defaultFields['minimal'] = array(
'public' => 'public',
@@ -1309,6 +1649,7 @@ class calendar_ical extends calendar_boupdate
$defaultFields['basic'] = $defaultFields['minimal'] + array(
'recur_exception' => 'recur_exception',
'priority' => 'priority',
+ 'status' => 'status',
);
$defaultFields['nexthaus'] = $defaultFields['basic'] + array(
@@ -1331,6 +1672,13 @@ class calendar_ical extends calendar_boupdate
'etag' => 'etag',
);
+ $defaultFields['funambol'] = $defaultFields['basic'] + array(
+ 'participants' => 'participants',
+ 'owner' => 'owner',
+ 'category' => 'category',
+ 'non_blocking' => 'non_blocking',
+ );
+
$defaultFields['evolution'] = $defaultFields['basic'] + array(
'participants' => 'participants',
'owner' => 'owner',
@@ -1458,7 +1806,7 @@ class calendar_ical extends calendar_boupdate
break;
case 'funambol':
- $this->supportedFields = $defaultFields['synthesis'];
+ $this->supportedFields = $defaultFields['funambol'];
break;
// the fallback for SyncML
@@ -1469,12 +1817,30 @@ class calendar_ical extends calendar_boupdate
}
}
- function icaltoegw($_vcalData, $cal_id=-1, $etag=null, $recur_date=0)
+ function icaltoegw($_vcalData)
{
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
+ array2string($_vcalData)."\n",3,$this->logfile);
+ }
+
$events = array();
$vcal = new Horde_iCalendar;
- if (!$vcal->parsevCalendar($_vcalData)) return false;
+ if (!$vcal->parsevCalendar($_vcalData))
+ {
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.
+ "(): No vCalendar Container found!\n",3,$this->logfile);
+ }
+ if ($this->tzid)
+ {
+ date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
+ }
+ return false;
+ }
$version = $vcal->getAttribute('VERSION');
if (!is_array($this->supportedFields)) $this->setSupportedFields();
@@ -1482,8 +1848,9 @@ class calendar_ical extends calendar_boupdate
{
if (is_a($component, 'Horde_iCalendar_vevent'))
{
- if ($event = $this->vevent2egw($component, $version, $this->supportedFields, $cal_id))
+ if ($event = $this->vevent2egw($component, $version, $this->supportedFields))
{
+ if ($this->isWholeDay($event)) $event['whole_day'] = true;
//common adjustments
if ($this->productManufacturer == '' && $this->productName == ''
&& !empty($event['recur_enddate']))
@@ -1491,6 +1858,7 @@ class calendar_ical extends calendar_boupdate
// syncevolution needs an adjusted recur_enddate
$event['recur_enddate'] = (int)$event['recur_enddate'] + 86400;
}
+
if ($event['recur_type'] != MCAL_RECUR_NONE)
{
// No reference or RECURRENCE-ID for the series master
@@ -1508,28 +1876,78 @@ class calendar_ical extends calendar_boupdate
}
$event['alarm'] = $alarms;
+ if (strpos($this->productName, 'palmos'))
+ {
+ if (isset($event['whole_day']))
+ {
+ $event['start'] = mktime(0, 0, 0,
+ date('m', $event['start']),
+ date('d', $event['start']),
+ date('Y', $event['start'])
+ );
+ $event['end'] = mktime(23, 59, 59,
+ date('m', $event['end']),
+ date('d', $event['end']),
+ date('Y', $event['end'])
+ );
+ if (isset($event['reference']))
+ {
+ $event['reference'] = mktime(0, 0, 0,
+ date('m', $event['reference']),
+ date('d', $event['reference']),
+ date('Y', $event['reference'])
+ );
+ }
+ foreach($event['recur_exception'] as $n => $date)
+ {
+ $event['recur_exception'][$n] = mktime(0, 0, 0,
+ date('m', $date),
+ date('d', $date),
+ date('Y', $date)
+ );
+ }
+ }
+ else
+ {
+ foreach(array('start','end','recur_enddate','reference') as $ts)
+ {
+ // we convert here from user-time to timestamps in server-time!
+ if (isset($event[$ts])) $event[$ts] = $event[$ts] ? $this->date2ts($event[$ts],true) : 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->date2ts($date,true);
+ }
+ }
+ }
+ // same with the alarms
+ if (isset($event['alarm']) && is_array($event['alarm']))
+ {
+ foreach($event['alarm'] as &$alarm)
+ {
+ $alarm['time'] = $this->date2ts($alarm['time'],true);
+ }
+ }
+ }
+ if (!empty($event['recur_enddate']))
+ {
+ // reset recure_enddate to 00:00:00
+ $event['recur_enddate'] = mktime(0, 0, 0,
+ date('m', $event['recur_enddate']),
+ date('d', $event['recur_enddate']),
+ date('Y', $event['recur_enddate'])
+ );
+ }
+
$events[] = $event;
}
}
}
- // decide what to return
- if (count($events) == 1)
- {
- $event = array_shift($events);
- if ($cal_id > 0) $event['id'] = $cal_id;
- if (!is_null($etag)) $event['etag'] = $etag;
- if ($recur_date) $event['reference'] = $recur_date;
-
- return array($event);
- }
- elseif (count($events) == 0 || $cal_id > 0 || !is_null($etag) || $recur_date)
- {
- // no events to return
- // or not allowed N:1 relation with params just meant for a single event
- return false;
- }
- else return $events;
+ return $events;
}
/**
@@ -1538,10 +1956,10 @@ class calendar_ical extends calendar_boupdate
* @param array $component VEVENT
* @param string $version vCal version (1.0/2.0)
* @param array $supportedFields supported fields of the device
- * @param int $cal_id id of existing event in the content (only used to merge categories)
+ *
* @return array|boolean event on success, false on failure
*/
- function vevent2egw(&$component, $version, $supportedFields, $cal_id=-1)
+ function vevent2egw(&$component, $version, $supportedFields)
{
if (!is_a($component, 'Horde_iCalendar_vevent')) return false;
@@ -1554,7 +1972,6 @@ class calendar_ical extends calendar_boupdate
}
$isDate = false;
- $hasOrganizer = false;
$event = array();
$alarms = array();
$vcardData = array(
@@ -1579,7 +1996,7 @@ class calendar_ical extends calendar_boupdate
$dtend_ts = is_numeric($attributes['value']) ? $attributes['value'] : $this->date2ts($attributes['value']);
if (date('H:i:s',$dtend_ts) == '00:00:00')
{
- $dtend_ts -= 60;
+ $dtend_ts -= 1;
}
$vcardData['end'] = $dtend_ts;
}
@@ -1602,7 +2019,7 @@ class calendar_ical extends calendar_boupdate
$vcardData['public'] = (int)(strtolower($attributes['value']) == 'public');
break;
case 'DESCRIPTION':
- $vcardData['description'] = $attributes['value'];
+ $vcardData['description'] = str_replace("\r\n", "\n", $attributes['value']);
if (preg_match('/\s*\[UID:(.+)?\]/Usm', $attributes['value'], $matches))
{
if (!isset($vCardData['uid'])
@@ -1617,10 +2034,11 @@ class calendar_ical extends calendar_boupdate
$vcardData['reference'] = $attributes['value'];
break;
case 'LOCATION':
- $vcardData['location'] = $attributes['value'];
+ $vcardData['location'] = str_replace("\r\n", "\n", $attributes['value']);
break;
case 'RRULE':
$recurence = $attributes['value'];
+ $vcardData['recur_interval'] = 1;
$type = preg_match('/FREQ=([^;: ]+)/i',$recurence,$matches) ? $matches[1] : $recurence[0];
// vCard 2.0 values for all types
if (preg_match('/UNTIL=([0-9TZ]+)/',$recurence,$matches))
@@ -1633,8 +2051,7 @@ class calendar_ical extends calendar_boupdate
}
if (preg_match('/INTERVAL=([0-9]+)/',$recurence,$matches))
{
- // 1 is invalid,, egw uses 0 for interval
- $vcardData['recur_interval'] = (int) $matches[1] != 0 ? (int) $matches[1] : 0;
+ $vcardData['recur_interval'] = (int) $matches[1] ? (int) $matches[1] : 1;
}
$vcardData['recur_data'] = 0;
switch($type)
@@ -1642,26 +2059,27 @@ class calendar_ical extends calendar_boupdate
case 'W':
case 'WEEKLY':
$days = array();
- if (preg_match('/W(\d+) ([^ ]*)( ([^ ]*))?$/',$recurence, $recurenceMatches)) // 1.0
+ if (preg_match('/W(\d+) *((?i: [AEFHMORSTUW]{2})+)?( +([^ ]*))$/',$recurence, $recurenceMatches)) // 1.0
{
$vcardData['recur_interval'] = $recurenceMatches[1];
- if (empty($recurenceMatches[4]))
+ if (empty($recurenceMatches[2]))
{
- if ($recurenceMatches[2] != '#0')
- {
- $vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[2]);
- }
$days[0] = strtoupper(substr(date('D', $vcardData['start']),0,2));
}
else
{
$days = explode(' ',trim($recurenceMatches[2]));
-
- if ($recurenceMatches[4] != '#0')
- {
- $vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[4]);
- }
}
+
+ if (preg_match('/#(\d+)/',$recurenceMatches[4],$repeatMatches))
+ {
+ if ($repeatMatches[1]) $vcardData['recur_count'] = $repeatMatches[1];
+ }
+ else
+ {
+ $vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[4]);
+ }
+
$recur_days = $this->recur_days_1_0;
}
elseif (preg_match('/BYDAY=([^;: ]+)/',$recurence,$recurenceMatches)) // 2.0
@@ -1699,7 +2117,7 @@ class calendar_ical extends calendar_boupdate
break;
case 'D': // 1.0
- if (preg_match('/D(\d+) #(.\d)/', $recurence, $recurenceMatches))
+ if (preg_match('/D(\d+) #(\d+)/', $recurence, $recurenceMatches))
{
$vcardData['recur_interval'] = $recurenceMatches[1];
if ($recurenceMatches[2] > 0 && $vcardData['end'])
@@ -1709,7 +2127,7 @@ class calendar_ical extends calendar_boupdate
date('i', $vcardData['end']),
date('s', $vcardData['end']),
date('m', $vcardData['end']),
- date('d', $vcardData['end']) + ($recurenceMatches[2] * $vcardData['recur_interval']),
+ date('d', $vcardData['end']) + ($vcardData['recur_interval']*($recurenceMatches[2]-1)),
date('Y', $vcardData['end'])
);
}
@@ -1717,10 +2135,7 @@ class calendar_ical extends calendar_boupdate
elseif (preg_match('/D(\d+) (.*)/', $recurence, $recurenceMatches))
{
$vcardData['recur_interval'] = $recurenceMatches[1];
- if ($recurenceMatches[2] != '#0')
- {
- $vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[2]);
- }
+ $vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime(trim($recurenceMatches[2]));
}
else break;
@@ -1741,7 +2156,7 @@ class calendar_ical extends calendar_boupdate
break;
case 'M':
- if (preg_match('/MD(\d+) #(.\d)/', $recurence, $recurenceMatches))
+ if (preg_match('/MD(\d+)(?: [^ ])? #(\d+)/', $recurence, $recurenceMatches))
{
$vcardData['recur_type'] = MCAL_RECUR_MONTHLY_MDAY;
$vcardData['recur_interval'] = $recurenceMatches[1];
@@ -1751,7 +2166,7 @@ class calendar_ical extends calendar_boupdate
date('H', $vcardData['end']),
date('i', $vcardData['end']),
date('s', $vcardData['end']),
- date('m', $vcardData['end']) + ($recurenceMatches[2] * $vcardData['recur_interval']),
+ date('m', $vcardData['end']) + ($vcardData['recur_interval']*($recurenceMatches[2]-1)),
date('d', $vcardData['end']),
date('Y', $vcardData['end'])
);
@@ -1764,21 +2179,28 @@ class calendar_ical extends calendar_boupdate
{
$vcardData['recur_interval'] = $recurenceMatches[1];
}
- if ($recurenceMatches[2] != '#0')
- {
- $vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[2]);
- }
+ $vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime(trim($recurenceMatches[2]));
}
elseif (preg_match('/MP(\d+) (.*) (.*) (.*)/',$recurence, $recurenceMatches))
{
$vcardData['recur_type'] = MCAL_RECUR_MONTHLY_WDAY;
- if ($recurenceMatches[1] > 1)
+ $vcardData['recur_interval'] = $recurenceMatches[1];
+ if (preg_match('/#(\d+)/',$recurenceMatches[4],$recurenceMatches))
{
- $vcardData['recur_interval'] = $recurenceMatches[1];
+ if ($recurenceMatches[1])
+ {
+ $vcardData['recur_enddate'] = mktime(
+ date('H', $vcardData['end']),
+ date('i', $vcardData['end']),
+ date('s', $vcardData['end']),
+ date('m', $vcardData['start']) + ($vcardData['recur_interval']*($recurenceMatches[1]-1)),
+ date('d', $vcardData['start']),
+ date('Y', $vcardData['start']));
+ }
}
- if ($recurenceMatches[4] != '#0')
+ else
{
- $vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[4]);
+ $vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime(trim($recurenceMatches[4]));
}
}
break;
@@ -1819,7 +2241,7 @@ class calendar_ical extends calendar_boupdate
$vcardData['recur_interval'] = $recurenceMatches[1];
if ($recurenceMatches[2] != '#0')
{
- $vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[2]);
+ $vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime(trim($recurenceMatches[2]));
}
} else break;
@@ -1841,6 +2263,7 @@ class calendar_ical extends calendar_boupdate
}
break;
case 'EXDATE':
+ if (!$attributes['value']) break;
if ((isset($attributes['params']['VALUE'])
&& $attributes['params']['VALUE'] == 'DATE') ||
(!isset($attributes['params']['VALUE']) && $isDate))
@@ -1867,7 +2290,7 @@ class calendar_ical extends calendar_boupdate
}
break;
case 'SUMMARY':
- $vcardData['title'] = $attributes['value'];
+ $vcardData['title'] = str_replace("\r\n", "\n", $attributes['value']);
break;
case 'UID':
if (strlen($attributes['value']) >= $minimum_uid_length)
@@ -1886,7 +2309,9 @@ class calendar_ical extends calendar_boupdate
}
break;
case 'PRIORITY':
- if($this->productManufacturer == 'funambol')
+ if ($this->productManufacturer == 'funambol' &&
+ (strpos($this->productName, 'outlook') !== false
+ || strpos($this->productName, 'pocket pc') !== false))
{
$vcardData['priority'] = (int) $this->priority_funambol2egw[$attributes['value']];
}
@@ -1898,22 +2323,15 @@ class calendar_ical extends calendar_boupdate
case 'CATEGORIES':
if ($attributes['value'])
{
- if($version == '1.0')
- {
- $vcardData['category'] = $this->find_or_add_categories(explode(';',$attributes['value']), $cal_id);
- }
- else
- {
- $vcardData['category'] = $this->find_or_add_categories(explode(',',$attributes['value']), $cal_id);
- }
+ $vcardData['category'] = explode(',', $attributes['value']);
}
else
{
$vcardData['category'] = array();
}
break;
- case 'ORGANIZER': // will be written direct to the event
case 'ATTENDEE':
+ case 'ORGANIZER': // will be written direct to the event
if (isset($attributes['params']['PARTSTAT']))
{
$attributes['params']['STATUS'] = $attributes['params']['PARTSTAT'];
@@ -1921,10 +2339,11 @@ class calendar_ical extends calendar_boupdate
if (isset($attributes['params']['STATUS']))
{
$status = $this->status_ical2egw[strtoupper($attributes['params']['STATUS'])];
+ if (empty($status)) $status = 'X';
}
else
{
- $status = 0;
+ $status = 'X'; // client did not return the status
}
$cn = '';
if (preg_match('/MAILTO:([@.a-z0-9_-]+)|MAILTO:"?([.a-z0-9_ -]*)"?[ ]*<([@.a-z0-9_-]*)>/i',
@@ -1979,7 +2398,7 @@ class calendar_ical extends calendar_boupdate
{
//Horde::logMessage("vevent2egw: group participant $uid",
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
- if ($status != 'U')
+ if ($status != 'X' && $status != 'U')
{
// User tries to reply to the group invitiation
$members = $GLOBALS['egw']->accounts->members($uid, true);
@@ -1987,7 +2406,8 @@ class calendar_ical extends calendar_boupdate
{
//Horde::logMessage("vevent2egw: set status to " . $status,
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
- $event['participants'][$this->user] = $status;
+ $event['participants'][$this->user] =
+ calendar_so::combine_status($status);
}
$status = 'U'; // keep the group
}
@@ -2022,18 +2442,26 @@ class calendar_ical extends calendar_boupdate
switch($attributes['name'])
{
case 'ATTENDEE':
- if (!$status)
- {
- $status = ($uid == $event['owner'] ? 'A' : 'U');
- }
- if (!isset($attributes['params']['ROLE']) || $attributes['params']['ROLE'] != 'ORGANIZER')
+ if (!isset($event['participants'][$uid]) ||
+ $event['participants'][$uid][0] != 'A')
{
+ // for multiple entries the ACCEPT wins
// add quantity and role
- $event['participants'][$uid] = calendar_so::combine_status($status,$attributes['params']['X-EGROUPWARE-QUANTITY'],$attributes['params']['ROLE']);
- break;
+ $event['participants'][$uid] =
+ calendar_so::combine_status($status,
+ $attributes['params']['X-EGROUPWARE-QUANTITY'],
+ $attributes['params']['ROLE']);
}
+ break;
+
case 'ORGANIZER':
- $hasOrganizer = true;
+ if (isset($event['participants'][$uid]))
+ {
+ $status = $event['participants'][$uid];
+ calendar_so::split_status($status, $quantity, $role);
+ $event['participants'][$uid] =
+ calendar_so::combine_status($status, $quantity, 'CHAIR');
+ }
if (is_numeric($uid) && ($uid == $this->calendarOwner || !$this->calendarOwner))
{
// we can store the ORGANIZER as event owner
@@ -2041,11 +2469,14 @@ class calendar_ical extends calendar_boupdate
}
else
{
+ // we must insert a CHAIR participant to keep the ORGANIZER
$event['owner'] = $this->user;
- }
- if (!isset($event['participants'][$uid]))
- {
- $event['participants'][$uid] = 'A';
+ if (!isset($event['participants'][$uid]))
+ {
+ // save the ORGANIZER as event CHAIR
+ $event['participants'][$uid] =
+ calendar_so::combine_status('U', 1, 'CHAIR');
+ }
}
}
break;
@@ -2054,16 +2485,9 @@ class calendar_ical extends calendar_boupdate
// fall through
case 'LAST-MODIFIED': // will be written direct to the event
$event['modified'] = $attributes['value'];
- break;
}
}
- if (!$hasOrganizer && !isset($event['participants'][$this->user]))
- {
- // according to iCalendar standard, ORGANIZER not used for events in the own calendar
- $event['participants'][$this->user] = 'A';
- }
-
// check if the entry is a birthday
// this field is only set from NOKIA clients
$agendaEntryType = $component->getAttribute('X-EPOCAGENDAENTRYTYPE');
@@ -2083,16 +2507,6 @@ class calendar_ical extends calendar_boupdate
}
}
- if (!empty($vcardData['recur_enddate']))
- {
- // reset recure_enddate to 00:00:00 on the last day
- $vcardData['recur_enddate'] = mktime(0, 0, 0,
- date('m',$vcardData['recur_enddate']),
- date('d',$vcardData['recur_enddate']),
- date('Y',$vcardData['recur_enddate'])
- );
- }
-
$event['priority'] = 2; // default
$event['alarm'] = $alarms;
@@ -2133,27 +2547,38 @@ class calendar_ical extends calendar_boupdate
}
}
if ($this->calendarOwner) $event['owner'] = $this->calendarOwner;
+
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
+ array2string($event)."\n",3,$this->logfile);
+ }
//Horde::logMessage("vevent2egw:\n" . print_r($event, true),
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
- if ($this->log)
- {
- error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n".array2string($event)."\n",3,$this->logfile);
- }
return $event;
}
function search($_vcalData, $contentID=null, $relax=false)
{
- if (($events = $this->icaltoegw($_vcalData,!is_null($contentID) ? $contentID : -1)))
+ if (($events = $this->icaltoegw($_vcalData)))
{
// this function only supports searching a single event
if (count($events) == 1)
{
+ $filter = $relax ? 'relax' : 'check';
$event = array_shift($events);
- return $this->find_event($event, $relax);
+ if ($this->isWholeDay($event, true)) $event['whole_day'] = true;
+ $event['category'] = $this->find_or_add_categories($event['category'], $eventId);
+ if ($contentID) $event['id'] = $contentID;
+ return $this->find_event($event, $filter);
+ }
+ if ($this->log)
+ {
+ error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."() found:\n" .
+ array2string($events)."\n",3,$this->logfile);
}
}
- return false;
+ return array();
}
/**
@@ -2269,124 +2694,6 @@ class calendar_ical extends calendar_boupdate
}
}
- /**
- * 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-EXCEPTION-STATUS 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-EXCEPTION-STATUS or SERIES-EXCEPTION-PROPAGATE
- * the corresponding series master event array
- * NOTE: this param is false if event is of type SERIES-MASTER
- */
- private function get_event_info($event)
- {
- $type = 'SINGLE'; // default
- $return_master = false; //default
-
- if ($event['recur_type'] != MCAL_RECUR_NONE)
- {
- $type = 'SERIES-MASTER';
- }
- else
- {
- // SINGLE, SERIES-EXCEPTION OR SERIES-EXCEPTON-STATUS
- if (empty($event['uid']) && $event['id'] > 0 && ($stored_event = $this->read($event['id'])))
- {
- $event['uid'] = $stored_event['uid']; // restore the UID if it was not delivered
- }
-
- if (isset($event['uid'])
- && $event['reference']
- && ($master_event = $this->read($event['uid']))
- && isset($master_event['recur_type'])
- && $master_event['recur_type'] != MCAL_RECUR_NONE)
- {
- // SERIES-EXCEPTION OR SERIES-EXCEPTON-STATUS
- $return_master = true; // we have a valid master and can return it
-
- if (isset($event['id']) && $master_event['id'] != $event['id'])
- {
- $type = 'SERIES-EXCEPTION'; // this is an existing exception
- }
- else
- {
- $type = 'SERIES-EXCEPTION-STATUS'; // 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
- $recurrence_event = $master_event;
- $recurrence_event['start'] = $event['reference'];
- $recurrence_event['end'] = $event['reference'] + ($master_event['end'] - $master_event['start']);
- // check for changed data
- foreach (array('start','end','uid','title','location',
- 'priority','public','special','non_blocking') as $key)
- {
- if (!empty($event[$key]) && $recurrence_event[$key] != $event[$key])
- {
- if (isset($event['id']))
- {
- $type = 'SERIES-EXCEPTION-PROPAGATE';
- }
- else
- {
- $type = 'SERIES-EXCEPTION'; // this is a new exception
- }
- 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']);
- }
- }
- else
- {
- // SINGLE
- $type = 'SINGLE';
- }
- }
-
- // read existing event
- if (isset($event['id']))
- {
- $stored_event = $this->read($event['id']);
- }
-
- // check ACL
- if ($return_master)
- {
- $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' => is_array($stored_event) ? $stored_event : false,
- 'master_event' => $return_master ? $master_event : false,
- );
- }
-
/**
* generate and insert a VTIMEZONE entry to a vcalendar
*
diff --git a/calendar/inc/class.calendar_sif.inc.php b/calendar/inc/class.calendar_sif.inc.php
index 77e4fa664f..855101c4a8 100644
--- a/calendar/inc/class.calendar_sif.inc.php
+++ b/calendar/inc/class.calendar_sif.inc.php
@@ -21,50 +21,106 @@ class calendar_sif extends calendar_boupdate
var $sifMapping = array(
'Start' => 'start',
'End' => 'end',
- 'AllDayEvent' => 'alldayevent',
+ 'AllDayEvent' => 'alldayevent',
'Attendees' => '',
'BillingInformation' => '',
'Body' => 'description',
- 'BusyStatus' => '',
- 'Categories' => 'category',
+ 'BusyStatus' => '',
+ 'Categories' => 'category',
'Companies' => '',
- 'Importance' => 'priority',
- 'IsRecurring' => 'isrecurring',
+ 'Importance' => 'priority',
+ 'IsRecurring' => 'isrecurring',
'Location' => 'location',
- 'MeetingStatus' => '',
+ 'MeetingStatus' => '',
'Mileage' => '',
'ReminderMinutesBeforeStart' => 'reminderstart',
- 'ReminderSet' => 'reminderset',
- 'ReminderSoundFile' => '',
- 'ReminderOptions' => '',
- 'ReminderInterval' => '',
+ 'ReminderSet' => 'reminderset',
+ 'ReminderSoundFile' => '',
+ 'ReminderOptions' => '',
+ 'ReminderInterval' => '',
'ReminderRepeatCount' => '',
- 'Exceptions' => '',
+ 'Exceptions' => '',
'ReplyTime' => '',
- 'Sensitivity' => 'public',
+ 'Sensitivity' => 'public',
'Subject' => 'title',
- 'RecurrenceType' => 'recur_type',
+ 'RecurrenceType' => 'recur_type',
'Interval' => 'recur_interval',
- 'MonthOfYear' => '',
- 'DayOfMonth' => '',
- 'DayOfWeekMask' => 'recur_weekmask',
+ 'MonthOfYear' => '',
+ 'DayOfMonth' => '',
+ 'DayOfWeekMask' => 'recur_weekmask',
'Instance' => '',
- 'PatternStartDate' => '',
+ 'PatternStartDate' => '',
'NoEndDate' => 'recur_noenddate',
- 'PatternEndDate' => 'recur_enddate',
- 'Occurrences' => '',
+ 'PatternEndDate' => 'recur_enddate',
+ 'Occurrences' => '',
);
- // the calendar event array
+ /**
+ * the calendar event array for the XML Parser
+ */
var $event;
- // device specific settings
+ /**
+ * name and sorftware version of the Funambol client
+ *
+ * @var string
+ */
var $productName = 'mozilla plugin';
var $productSoftwareVersion = '0.3';
+
+ /**
+ * user preference: import all-day events as non blocking
+ *
+ * @var boolean
+ */
+ var $nonBlockingAllday = false;
+
+ /**
+ * user preference: attach UID entries to the DESCRIPTION
+ *
+ * @var boolean
+ */
var $uidExtension = false;
+ /**
+ * user preference: calendar to synchronize with
+ *
+ * @var int
+ */
+ var $calendarOwner = 0;
+
+ /**
+ * user preference: use server timezone for exports to device
+ *
+ * @var boolean
+ */
+ var $useServerTZ = false;
+
+ /**
+ * Device CTCap Properties
+ *
+ * @var array
+ */
+ var $clientProperties;
+
+ /**
+ * vCalendar Instance for parsing
+ *
+ * @var array
+ */
+ var $vCalendar;
+
+ /**
+ * Set Logging
+ *
+ * @var boolean
+ */
+ var $log = false;
+ var $logfile="/tmp/log-sifcal";
+
+
// constants for recurence type
- const olRecursDaily = 0;
+ const olRecursDaily = 0;
const olRecursWeekly = 1;
const olRecursMonthly = 2;
const olRecursMonthNth = 3;
@@ -72,28 +128,50 @@ class calendar_sif extends calendar_boupdate
const olRecursYearNth = 6;
// constants for weekdays
- const olSunday = 1;
- const olMonday = 2;
- const olTuesday = 4;
- const olWednesday = 8;
- const olThursday = 16;
- const olFriday = 32;
- const olSaturday = 64;
+ const olSunday = 1;
+ const olMonday = 2;
+ const olTuesday = 4;
+ const olWednesday = 8;
+ const olThursday = 16;
+ const olFriday = 32;
+ const olSaturday = 64;
// standard headers
const xml_decl = '';
const SIF_decl = '
".__METHOD__.'('.array2string($ids).') = '.array2string($etags)."
\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 ''.__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).")
\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 ''.__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; diff --git a/calendar/templates/default/images/forward.gif b/calendar/templates/default/images/forward.gif new file mode 100644 index 0000000000..7a5df0b757 Binary files /dev/null and b/calendar/templates/default/images/forward.gif differ diff --git a/calendar/templates/default/images/forward.png b/calendar/templates/default/images/forward.png new file mode 100644 index 0000000000..7986c31f45 Binary files /dev/null and b/calendar/templates/default/images/forward.png differ diff --git a/phpgwapi/inc/horde/Horde/iCalendar.php b/phpgwapi/inc/horde/Horde/iCalendar.php index f6e99fd9df..6c5454d473 100644 --- a/phpgwapi/inc/horde/Horde/iCalendar.php +++ b/phpgwapi/inc/horde/Horde/iCalendar.php @@ -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()) {