Pseudo recurrence exception handling hacks; vCal 1.0 extensions

This commit is contained in:
Jörg Lehrke 2009-10-25 18:22:01 +00:00
parent 60a7f69b57
commit 6d3bf02d1c

View File

@ -183,6 +183,7 @@ class calendar_ical extends calendar_boupdate
'CATEGORIES' => 'category',
'UID' => 'uid',
'RECURRENCE-ID' => 'recurrence',
'SEQUENCE' => 'etag',
);
if (!is_array($this->supportedFields)) $this->setSupportedFields();
@ -227,6 +228,11 @@ class calendar_ical extends calendar_boupdate
}
$event['recur_type'] = MCAL_RECUR_NONE;
}
elseif ($event['recur_enddate'])
{
$recur_enddate = (int)$event['recur_enddate'];
$recur_enddate += 24 * 60 * 60 - 1;
}
if ($this->log) error_log(__FILE__.'('.__LINE__.'): '.__METHOD__.' '.array2string($event)."\n",3,$this->logfile);
@ -335,14 +341,13 @@ class calendar_ical extends calendar_boupdate
break;
case 'ORGANIZER': // according to iCalendar standard, ORGANIZER not used for events in the own calendar
//if ($event['owner'] != $this->user)
//if (!isset($event['participants'][$event['owner']]) || count($event['participants']) > 1)
//{
if ($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')).'"';
//}
}
break;
case 'DTSTART':
@ -386,7 +391,8 @@ class calendar_ical extends calendar_boupdate
case 'RRULE':
if ($event['recur_type'] == MCAL_RECUR_NONE) break; // no recuring event
if ($version == '1.0') {
if ($version == '1.0')
{
$interval = ($event['recur_interval'] > 1) ? $event['recur_interval'] : 1;
$rrule = array('FREQ' => $this->recur_egw2ical_1_0[$event['recur_type']].$interval);
switch ($event['recur_type'])
@ -413,8 +419,6 @@ class calendar_ical extends calendar_boupdate
if ($event['recur_enddate'])
{
$recur_enddate = (int)$event['recur_enddate'];
$recur_enddate += 24 * 60 * 60 - 1;
$rrule['UNTIL'] = $vcal->_exportDateTime($recur_enddate);
}
else
@ -451,19 +455,10 @@ class calendar_ical extends calendar_boupdate
}
if ($event['recur_enddate'])
{
// We use end of day in vCal
$recur_enddate = (int)$event['recur_enddate'];
$recur_enddate += 24 * 60 * 60 - 1;
if ($this->isWholeDay($event))
{
$rrule['UNTIL'] = date('Ymd', $recur_enddate);
}
else
{
// UNTIL should be a UTC timestamp
$rrule['UNTIL'] = $vcal->_exportDateTime($recur_enddate);
}
}
}
$attributes['RRULE'] = '';
$parameters['RRULE'] = $rrule;
break;
@ -545,9 +540,12 @@ class calendar_ical extends calendar_boupdate
break;
case 'TRANSP':
if ($version == '1.0') {
if ($version == '1.0')
{
$attributes['TRANSP'] = ($event['non_blocking'] ? 1 : 0);
} else {
}
else
{
$attributes['TRANSP'] = ($event['non_blocking'] ? 'TRANSPARENT' : 'OPAQUE');
}
break;
@ -561,13 +559,17 @@ class calendar_ical extends calendar_boupdate
break;
case 'RECURRENCE-ID':
if ($version == '1.0')
{
$icalFieldName = 'X-RECURRENCE-ID';
}
if ($recur_date)
{
// We handle a status only exception
if ($this->isWholeDay($event))
{
$arr = $this->date2array($recur_date);
$vevent->setAttribute('RECURRENCE-ID', array(
$vevent->setAttribute($icalFieldName, array(
'year' => $arr['year'],
'month' => $arr['month'],
'mday' => $arr['day']),
@ -578,12 +580,12 @@ class calendar_ical extends calendar_boupdate
{
if ($servertime)
{
$attributes['RECURRENCE-ID'] = date('Ymd\THis', $recur_date);
if ($serverTZ) $parameters['RECURRENCE-ID']['TZID'] = $serverTZ;
$attributes[$icalFieldName] = date('Ymd\THis', $recur_date);
if ($serverTZ) $parameters[$icalFieldName]['TZID'] = $serverTZ;
}
else
{
$attributes['RECURRENCE-ID'] = $recur_date;
$attributes[$icalFieldName] = $recur_date;
}
}
}
@ -595,7 +597,7 @@ class calendar_ical extends calendar_boupdate
if ($this->isWholeDay($revent))
{
$arr = $this->date2array($event['recurrence']);
$vevent->setAttribute('RECURRENCE-ID', array(
$vevent->setAttribute($icalFieldName, array(
'year' => $arr['year'],
'month' => $arr['month'],
'mday' => $arr['day']),
@ -606,12 +608,12 @@ class calendar_ical extends calendar_boupdate
{
if ($servertime)
{
$attributes['RECURRENCE-ID'] = date('Ymd\THis', $event['recurrence']);
if ($serverTZ) $parameters['RECURRENCE-ID']['TZID'] = $serverTZ;
$attributes[$icalFieldName] = date('Ymd\THis', $event['recurrence']);
if ($serverTZ) $parameters[$icalFieldName]['TZID'] = $serverTZ;
}
else
{
$attributes['RECURRENCE-ID'] = $event['recurrence'];
$attributes[$icalFieldName] = $event['recurrence'];
}
}
unset($revent);
@ -619,19 +621,24 @@ class calendar_ical extends calendar_boupdate
break;
default:
if (isset($this->clientProperties[$icalFieldName]['Size'])) {
if (isset($this->clientProperties[$icalFieldName]['Size']))
{
$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);
} else {
}
else
{
$size = -1;
$noTruncate = false;
}
$value = $event[$egwFieldName];
$cursize = strlen($value);
if (($size > 0) && $cursize > $size) {
if ($noTruncate) {
if ($size > 0 && $cursize > $size)
{
if ($noTruncate)
{
Horde::logMessage("vCalendar $icalFieldName omitted due to maximum size $size",
__FILE__, __LINE__, PEAR_LOG_WARNING);
continue; // skip field
@ -641,21 +648,27 @@ class calendar_ical extends calendar_boupdate
Horde::logMessage("vCalendar $icalFieldName truncated to maximum size $size",
__FILE__, __LINE__, PEAR_LOG_INFO);
}
if (!empty($value) || ($size >= 0 && !$noTruncate)) {
if (!empty($value) || ($size >= 0 && !$noTruncate))
{
$attributes[$icalFieldName] = $value;
}
break;
}
}
if($this->productManufacturer == 'nokia') {
if($event['special'] == '1') {
if ($this->productManufacturer == 'nokia')
{
if ($event['special'] == '1')
{
$attributes['X-EPOCAGENDAENTRYTYPE'] = 'ANNIVERSARY';
$attributes['DTEND'] = $attributes['DTSTART'];
}
elseif ($event['special'] == '2') {
elseif ($event['special'] == '2')
{
$attributes['X-EPOCAGENDAENTRYTYPE'] = 'EVENT';
} else {
}
else
{
$attributes['X-EPOCAGENDAENTRYTYPE'] = 'APPOINTMENT';
}
}
@ -676,7 +689,7 @@ class calendar_ical extends calendar_boupdate
foreach ($event['alarm'] as $alarmID => $alarmData)
{
// skip alarms being set for all users or alarms owned by other users
if($alarmData['all'] == true || $alarmData['owner'] != $GLOBALS['egw_info']['user']['account_id'])
if ($alarmData['all'] == true || $alarmData['owner'] != $this->user)
{
continue;
}
@ -795,7 +808,7 @@ class calendar_ical extends calendar_boupdate
{
if ($this->log) error_log(__LINE__.__METHOD__.__FILE__.array2string($_vcalData)."\n",3,$this->logfile);
if(!$events = $this->icaltoegw($_vcalData,$cal_id,$etag,$recur_date))
if (!($events = $this->icaltoegw($_vcalData,$cal_id,$etag,$recur_date)))
{
return false;
}
@ -821,14 +834,14 @@ class calendar_ical extends calendar_boupdate
if (!isset($event['owner'])
|| !$this->check_perms(EGW_ACL_ADD, 0, $event['owner']))
{
$event['owner'] = $GLOBALS['egw_info']['user']['account_id'];
$event['owner'] = $this->user;
}
// add ourself to new events as participant
if (!isset($this->supportedFields['participants'])
||!isset($event['participants'][$GLOBALS['egw_info']['user']['account_id']]))
||!isset($event['participants'][$this->user]))
{
$event['participants'][$GLOBALS['egw_info']['user']['account_id']] = 'A';
$event['participants'][$this->user] = 'A';
}
}
@ -957,7 +970,7 @@ class calendar_ical extends calendar_boupdate
}
$event_to_store = $event; // prevent $event from being changed by update method
$updated_id = $this->update($event_to_store, true);
$updated_id = $this->update($event_to_store, true, true, false, false);
unset($event_to_store);
}
break;
@ -980,7 +993,7 @@ class calendar_ical extends calendar_boupdate
// 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
$updated_id = $this->update($event_to_store, true);
$updated_id = $this->update($event_to_store, true, true, false, false);
unset($event_to_store);
}
@ -1051,7 +1064,7 @@ class calendar_ical extends calendar_boupdate
foreach ($event_info['stored_event']['alarm'] as $alarm_id => $alarm_data)
{
// only touch own alarms
if($alarm_data['all'] == false && $alarm_data['owner'] == $GLOBALS['egw_info']['user']['account_id'])
if ($alarm_data['all'] == false && $alarm_data['owner'] == $this->user)
{
$this->delete_alarm($alarm_id);
}
@ -1071,7 +1084,7 @@ class calendar_ical extends calendar_boupdate
{
$alarm['time'] = $event['start'] - $alarm['offset'];
}
$alarm['owner'] = $GLOBALS['egw_info']['user']['account_id'];
$alarm['owner'] = $this->user;
$alarm['all'] = false;
$this->save_alarm($event_info['stored_event']['id'], $alarm);
}
@ -1194,31 +1207,38 @@ class calendar_ical extends calendar_boupdate
function setSupportedFields($_productManufacturer='', $_productName='')
{
$state = &$_SESSION['SyncML.state'];
if (isset($state)) {
if (isset($state))
{
$deviceInfo = $state->getClientDeviceInfo();
}
// store product manufacturer and name, to be able to use it elsewhere
if ($_productManufacturer) {
if ($_productManufacturer)
{
$this->productManufacturer = strtolower($_productManufacturer);
$this->productName = strtolower($_productName);
}
if(isset($deviceInfo) && is_array($deviceInfo)) {
if (isset($deviceInfo) && is_array($deviceInfo))
{
if (isset($deviceInfo['uidExtension']) &&
$deviceInfo['uidExtension']){
$deviceInfo['uidExtension'])
{
$this->uidExtension = true;
}
if (isset($deviceInfo['nonBlockingAllday']) &&
$deviceInfo['nonBlockingAllday']){
$deviceInfo['nonBlockingAllday'])
{
$this->nonBlockingAllday = true;
}
if (!isset($this->productManufacturer) ||
$this->productManufacturer == '' ||
$this->productManufacturer == 'file') {
$this->productManufacturer == 'file')
{
$this->productManufacturer = strtolower($deviceInfo['manufacturer']);
}
if(!isset($this->productName) || $this->productName == '') {
if (!isset($this->productName) || $this->productName == '')
{
$this->productName = strtolower($deviceInfo['model']);
}
}
@ -1262,6 +1282,7 @@ class calendar_ical extends calendar_boupdate
'non_blocking' => 'non_blocking',
'uid' => 'uid',
'recurrence' => 'recurrence',
'etag' => 'etag',
);
$defaultFields['evolution'] = $defaultFields['basic'] + array(
@ -1278,6 +1299,7 @@ class calendar_ical extends calendar_boupdate
'non_blocking' => 'non_blocking',
'uid' => 'uid',
'recurrence' => 'recurrence',
'etag' => 'etag',
);
@ -1457,10 +1479,7 @@ class calendar_ical extends calendar_boupdate
// or not allowed N:1 relation with params just meant for a single event
return false;
}
else
{
return $events;
}
else return $events;
}
/**
@ -1485,23 +1504,34 @@ class calendar_ical extends calendar_boupdate
}
$isDate = false;
$hasOrganizer = false;
$event = array();
$alarms = array();
$vcardData = array(
'recur_type' => MCAL_RECUR_NONE,
'recur_exception' => array(),
);
// we parse DTSTART first
// we parse DTSTART and DTEND first
foreach ($component->_attributes as $attributes)
{
if ($attributes['name'] == 'DTSTART')
switch ($attributes['name'])
{
case 'DTSTART':
if (isset($attributes['params']['VALUE'])
&& $attributes['params']['VALUE'] == 'DATE')
{
$isDate = true;
}
$vcardData['start'] = $attributes['value'];
$dtstart_ts = is_numeric($attributes['value']) ? $attributes['value'] : $this->date2ts($attributes['value']);
$vcardData['start'] = $dtstart_ts;
break;
case 'DTEND':
$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;
}
$vcardData['end'] = $dtend_ts;
}
}
if (!isset($vcardData['start'])) return false; // not a valid entry
@ -1523,26 +1553,17 @@ class calendar_ical extends calendar_boupdate
break;
case 'DESCRIPTION':
$vcardData['description'] = $attributes['value'];
if (preg_match('/\s*\[UID:(.+)?\]/Usm', $attributes['value'], $matches)) {
if (preg_match('/\s*\[UID:(.+)?\]/Usm', $attributes['value'], $matches))
{
if (!isset($vCardData['uid'])
&& strlen($matches[1]) >= $minimum_uid_length) {
&& strlen($matches[1]) >= $minimum_uid_length)
{
$vcardData['uid'] = $matches[1];
}
}
break;
case 'DTEND':
$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;
}
$vcardData['end'] = $dtend_ts;
break;
case 'RECURRENCE-ID':
// ToDo or check:
// - do we need to set reference (cal_id of orginal series)
// - do we need to add that recurrence as recure exception to the original series
// --> original series should be found by searching for a series with same UID (backend)
// Joerg's answers: All this is handled within importVCal() for SyncML.
case 'X-RECURRENCE-ID':
$vcardData['recurrence'] = $attributes['value'];
break;
case 'LOCATION':
@ -1565,23 +1586,32 @@ class calendar_ical extends calendar_boupdate
// 1 is invalid,, egw uses 0 for interval
$vcardData['recur_interval'] = (int) $matches[1] != 0 ? (int) $matches[1] : 0;
}
if (!isset($vcardData['start'])) // it might not yet be set, because the RRULE is before it
{
$vcardData['start'] = self::_get_attribute($component->_attributes,'DTSTART');
$vcardData['end'] = self::_get_attribute($component->_attributes,'DTEND');
}
$vcardData['recur_data'] = 0;
switch($type)
{
case 'W':
case 'WEEKLY':
$days = array();
if(preg_match('/W(\d+) (.*) (.*)/',$recurence, $recurenceMatches)) // 1.0
if (preg_match('/W(\d+) ([^ ]*)( ([^ ]*))?$/',$recurence, $recurenceMatches)) // 1.0
{
$vcardData['recur_interval'] = $recurenceMatches[1];
if (empty($recurenceMatches[4]))
{
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[3] != '#0')
$vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[3]);
if ($recurenceMatches[4] != '#0')
{
$vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[4]);
}
}
$recur_days = $this->recur_days_1_0;
}
elseif (preg_match('/BYDAY=([^;: ]+)/',$recurence,$recurenceMatches)) // 2.0
@ -1596,8 +1626,10 @@ class calendar_ical extends calendar_boupdate
}
if ($days)
{
foreach($recur_days as $id => $day) {
if (in_array(strtoupper(substr($day,0,2)),$days)) {
foreach ($recur_days as $id => $day)
{
if (in_array(strtoupper(substr($day,0,2)),$days))
{
$vcardData['recur_data'] |= $id;
}
}
@ -1614,9 +1646,11 @@ 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']) {
if ($recurenceMatches[2] > 0 && $vcardData['end'])
{
$vcardData['recur_enddate'] = mktime(
date('H', $vcardData['end']),
date('i', $vcardData['end']),
@ -1626,14 +1660,17 @@ class calendar_ical extends calendar_boupdate
date('Y', $vcardData['end'])
);
}
} elseif(preg_match('/D(\d+) (.*)/', $recurence, $recurenceMatches)) {
}
elseif (preg_match('/D(\d+) (.*)/', $recurence, $recurenceMatches))
{
$vcardData['recur_interval'] = $recurenceMatches[1];
if($recurenceMatches[2] != '#0') {
if ($recurenceMatches[2] != '#0')
{
$vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[2]);
}
} else {
break;
}
else break;
// fall-through
case 'DAILY': // 2.0
$vcardData['recur_type'] = MCAL_RECUR_DAILY;
@ -1648,10 +1685,12 @@ 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];
if($recurenceMatches[2] > 0 && $vcardData['end']) {
if ($recurenceMatches[2] > 0 && $vcardData['end'])
{
$vcardData['recur_enddate'] = mktime(
date('H', $vcardData['end']),
date('i', $vcardData['end']),
@ -1719,7 +1758,8 @@ class calendar_ical extends calendar_boupdate
elseif (preg_match('/YM(\d+) (.*)/',$recurence, $recurenceMatches))
{
$vcardData['recur_interval'] = $recurenceMatches[1];
if($recurenceMatches[2] != '#0') {
if ($recurenceMatches[2] != '#0')
{
$vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[2]);
}
} else break;
@ -1819,7 +1859,8 @@ class calendar_ical extends calendar_boupdate
}
$cn = '';
if (preg_match('/MAILTO:([@.a-z0-9_-]+)|MAILTO:"?([.a-z0-9_ -]*)"?[ ]*<([@.a-z0-9_-]*)>/i',
$attributes['value'],$matches)) {
$attributes['value'],$matches))
{
$email = $matches[1] ? $matches[1] : $matches[3];
$cn = isset($matches[2]) ? $matches[2]: '';
}
@ -1881,7 +1922,7 @@ class calendar_ical extends calendar_boupdate
}
elseif ($attributes['value'] == 'Unknown')
{
$uid = $GLOBALS['egw_info']['user']['account_id'];
$uid = $this->user;
}
elseif ($email && ($uid = $GLOBALS['egw']->accounts->name2id($email,'account_email')))
{
@ -1915,10 +1956,14 @@ class calendar_ical extends calendar_boupdate
{
$event['participants'][$uid] = ($uid == $event['owner'] ? 'A' : 'U');
}
if (!isset($attributes['params']['ROLE']) || $attributes['params']['ROLE'] != 'ORGANIZER')
{
// add quantity and role
$event['participants'][$uid] = calendar_so::combine_status($status,$attributes['params']['X-EGROUPWARE-QUANTITY'],$attributes['params']['ROLE']);
break;
}
case 'ORGANIZER':
$hasOrganizer = true;
if (is_numeric($uid))
{
$event['owner'] = $uid;
@ -1941,6 +1986,13 @@ class calendar_ical extends calendar_boupdate
}
}
if (!$hasOrganizer && $this->productManufacturer == 'groupdav'
&& !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');
@ -2016,7 +2068,7 @@ class calendar_ical extends calendar_boupdate
function search($_vcalData, $contentID=null, $relax=false)
{
if($events = $this->icaltoegw($_vcalData,!is_null($contentID) ? $contentID : -1))
if (($events = $this->icaltoegw($_vcalData,!is_null($contentID) ? $contentID : -1)))
{
// this function only supports searching a single event
if (count($events) == 1)
@ -2127,7 +2179,9 @@ class calendar_ical extends calendar_boupdate
if (!isset($new_event['participants'][$userid])){
// Attendee will be deleted this way
$new_event['participants'][$userid] = 'G';
} elseif ($new_event['participants'][$userid] == $status){
}
elseif ($new_event['participants'][$userid] == $status)
{
// Same status -- nothing to do.
unset($new_event['participants'][$userid]);
}
@ -2135,7 +2189,7 @@ class calendar_ical extends calendar_boupdate
// write the changes
foreach ($new_event['participants'] as $userid => $status)
{
$this->set_status($old_event, $userid, $status, $recur_date, true);
$this->set_status($old_event, $userid, $status, $recur_date, true, false);
}
}
@ -2181,8 +2235,7 @@ class calendar_ical extends calendar_boupdate
&& $event['recurrence']
&& ($master_event = $this->read($event['uid']))
&& isset($master_event['recur_type'])
&& $master_event['recur_type'] != MCAL_RECUR_NONE
)
&& $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