From b9c2d505f26a2236406dedddc1b8e88a9e5095ae Mon Sep 17 00:00:00 2001 From: Lars Kneschke Date: Tue, 16 May 2006 17:14:24 +0000 Subject: [PATCH] syncml fixes --- calendar/inc/class.boical.inc.php | 1032 ++++++++++++++++++++++++ calendar/inc/class.sifcalendar.inc.php | 503 ++++++++++++ 2 files changed, 1535 insertions(+) create mode 100644 calendar/inc/class.boical.inc.php create mode 100644 calendar/inc/class.sifcalendar.inc.php diff --git a/calendar/inc/class.boical.inc.php b/calendar/inc/class.boical.inc.php new file mode 100644 index 0000000000..5d21b8f507 --- /dev/null +++ b/calendar/inc/class.boical.inc.php @@ -0,0 +1,1032 @@ + * + * -------------------------------------------- * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License. * + \**************************************************************************/ + + /* $Id$ */ + + require_once EGW_SERVER_ROOT.'/calendar/inc/class.bocalupdate.inc.php'; + require_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/Horde/iCalendar.php'; + + /** + * iCal import and export via Horde iCalendar classes + * + * @package calendar + * @author Lars Kneschke + * @author Ralf Becker + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + */ + class boical extends bocalupdate + { + /** + * @var array $supportedFields array containing the supported fields of the importing device + */ + var $supportedFields; + + var $recur_days_1_0 = array( + MCAL_M_MONDAY => 'MO', + MCAL_M_TUESDAY => 'TU', + MCAL_M_WEDNESDAY => 'WE', + MCAL_M_THURSDAY => 'TH', + MCAL_M_FRIDAY => 'FR', + MCAL_M_SATURDAY => 'SA', + MCAL_M_SUNDAY => 'SU', + ); + + /** + * @var array $status_egw2ical conversation of the participant status egw => ical + */ + var $status_egw2ical = array( + 'U' => 'NEEDS-ACTION', + 'A' => 'ACCEPTED', + 'R' => 'DECLINED', + 'T' => 'TENTATIVE', + ); + /** + * @var array conversation of the participant status ical => egw + */ + var $status_ical2egw = array( + 'NEEDS-ACTION' => 'U', + 'ACCEPTED' => 'A', + 'DECLINED' => 'R', + 'TENTATIVE' => 'T', + ); + + /** + * @var array $status_ical2egw conversation of the priority egw => ical + */ + var $priority_egw2ical = array( + 0 => 0, // undefined + 1 => 9, // low + 2 => 5, // normal + 3 => 1, // high + ); + /** + * @var array $status_ical2egw conversation of the priority ical => egw + */ + var $priority_ical2egw = array( + 0 => 0, // undefined + 9 => 1, 8 => 1, 7 => 1, 6 => 1, // low + 5 => 2, // normal + 4 => 3, 3 => 3, 2 => 3, 1 => 3, // high + ); + + /** + * @var array $recur_egw2ical_2_0 converstaion of egw recur-type => ical FREQ + */ + var $recur_egw2ical_2_0 = array( + MCAL_RECUR_DAILY => 'DAILY', + MCAL_RECUR_WEEKLY => 'WEEKLY', + MCAL_RECUR_MONTHLY_MDAY => 'MONTHLY', // BYMONHTDAY={1..31} + MCAL_RECUR_MONTHLY_WDAY => 'MONTHLY', // BYDAY={1..5}{MO..SO} + MCAL_RECUR_YEARLY => 'YEARLY', + ); + + /** + * @var array $recur_egw2ical_1_0 converstaion of egw recur-type => ical FREQ + */ + var $recur_egw2ical_1_0 = array( + MCAL_RECUR_DAILY => 'D', + MCAL_RECUR_WEEKLY => 'W', + MCAL_RECUR_MONTHLY_MDAY => 'MD', // BYMONHTDAY={1..31} + MCAL_RECUR_MONTHLY_WDAY => 'MP', // BYDAY={1..5}{MO..SO} + MCAL_RECUR_YEARLY => 'YM', + ); + + /** + * Exports one calendar event to an iCalendar item + * + * @param int/array $events (array of) cal_id or array of the events + * @param string $method='PUBLISH' + * @return string/boolean string with vCal or false on error (eg. no permission to read the event) + */ + function &exportVCal($events,$version='1.0', $method='PUBLISH') + { + $egwSupportedFields = array( + 'CLASS' => array('dbName' => 'public'), + 'SUMMARY' => array('dbName' => 'title'), + 'DESCRIPTION' => array('dbName' => 'description'), + 'LOCATION' => array('dbName' => 'location'), + 'DTSTART' => array('dbName' => 'start'), + 'DTEND' => array('dbName' => 'end'), + 'ORGANIZER' => array('dbName' => 'owner'), + 'ATTENDEE' => array('dbName' => 'participants'), + 'RRULE' => array('dbName' => 'recur_type'), + 'EXDATE' => array('dbName' => 'recur_exception'), + 'PRIORITY' => array('dbName' => 'priority'), + 'TRANSP' => array('dbName' => 'non_blocking'), + 'CATEGORIES' => array('dbName' => 'category'), + ); + if(!is_array($this->supportedFields)) + { + $this->setSupportedFields(); + } + $vcal = &new Horde_iCalendar; + $vcal->setAttribute('PRODID','-//eGroupWare//NONSGML eGroupWare Calendar '.$GLOBALS['egw_info']['apps']['calendar']['version'].'//'. + strtoupper($GLOBALS['egw_info']['user']['preferences']['common']['lang'])); + $vcal->setAttribute('VERSION',$version); + $vcal->setAttribute('METHOD',$method); + + if (!is_array($events)) $events = array($events); + + foreach($events as $event) + { + if (!is_array($event) && !($event = $this->read($event,null,false,'server'))) // server = timestamp in server-time(!) + { + return false; // no permission to read $cal_id + } + //_debug_array($event); + + $eventGUID = $GLOBALS['egw']->common->generate_uid('calendar',$event['id']); + + $vevent = Horde_iCalendar::newComponent('VEVENT',$vcal); + $parameters = $attributes = array(); + + foreach($egwSupportedFields as $icalFieldName => $egwFieldInfo) + { + if($this->supportedFields[$egwFieldInfo['dbName']]) + { + switch($icalFieldName) + { + case 'ATTENDEE': + foreach((array)$event['participants'] as $uid => $status) + { + // ToDo, this needs to deal with resources too!!! + if (!is_numeric($uid)) continue; + + $mailto = $GLOBALS['egw']->accounts->id2name($uid,'account_email'); + $cn = trim($GLOBALS['egw']->accounts->id2name($uid,'account_firstname'). ' ' . + $GLOBALS['egw']->accounts->id2name($uid,'account_lastname')); + $attributes['ATTENDEE'][] = $mailto ? 'MAILTO:'.$mailto : ''; + // ROLE={CHAIR|REQ-PARTICIPANT|OPT-PARTICIPANT|NON-PARTICIPANT} NOT used by eGW atm. + $role = $uid == $event['owner'] ? 'CHAIR' : 'REQ-PARTICIPANT'; + // 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. + $status = $this->status_egw2ical[$status]; + // CUTYPE={INDIVIDUAL|GROUP|RESOURCE|ROOM|UNKNOWN} + $cutype = $GLOBALS['egw']->accounts->get_type($uid) == 'g' ? 'GROUP' : 'INDIVIDUAL'; + $parameters['ATTENDEE'][] = array( + 'CN' => $cn, + 'ROLE' => $role, + 'PARTSTAT' => $status, + 'CUTYPE' => $cutype, + 'RSVP' => $rsvp, + ); + } + break; + + case 'CLASS': + $attributes['CLASS'] = $event['public'] ? 'PUBLIC' : 'PRIVATE'; + break; + + case 'ORGANIZER': // according to iCalendar standard, ORGANIZER not used for events in the own calendar + if (!isset($event['participants'][$event['owner']]) || count($event['participants']) > 1) + { + $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 'DTEND': + if(date('H:i:s',$event['end']) == '23:59:59') $event['end']++; + $attributes[$icalFieldName] = $event['end']; + break; + + case 'RRULE': + if ($event['recur_type'] == MCAL_RECUR_NONE) break; // no recuring event + 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']) + { + case MCAL_RECUR_WEEKLY: + $days = array(); + foreach($this->recur_days_1_0 as $id => $day) + { + if ($event['recur_data'] & $id) $days[] = strtoupper(substr($day,0,2)); + } + $rrule['BYDAY'] = implode(' ',$days); + $rrule['FREQ'] = $rrule['FREQ'].' '.$rrule['BYDAY']; + break; + + case MCAL_RECUR_MONTHLY_MDAY: // date of the month: BYMONTDAY={1..31} + break; + + case MCAL_RECUR_MONTHLY_WDAY: // weekday of the month: BDAY={1..5}{MO..SO} + $rrule['BYDAY'] = (1 + (int) ((date('d',$event['start'])-1) / 7)).'+ '. + strtoupper(substr(date('l',$event['start']),0,2)); + $rrule['FREQ'] = $rrule['FREQ'].' '.$rrule['BYDAY']; + break; + } + $rrule['UNTIL'] = ($event['recur_enddate']) ? date('Ymd',$event['recur_enddate']) : '#0'; + + $attributes['RRULE'] = $rrule['FREQ'].' '.$rrule['UNTIL']; + } else { + $rrule = array('FREQ' => $this->recur_egw2ical_2_0[$event['recur_type']]); + switch ($event['recur_type']) + { + case MCAL_RECUR_WEEKLY: + $days = array(); + foreach($this->recur_days as $id => $day) + { + if ($event['recur_data'] & $id) $days[] = strtoupper(substr($day,0,2)); + } + $rrule['BYDAY'] = implode(',',$days); + break; + + case MCAL_RECUR_MONTHLY_MDAY: // date of the month: BYMONTDAY={1..31} + $rrule['BYMONTHDAY'] = (int) date('d',$event['start']); + break; + + case MCAL_RECUR_MONTHLY_WDAY: // weekday of the month: BDAY={1..5}{MO..SO} + $rrule['BYDAY'] = (1 + (int) ((date('d',$event['start'])-1) / 7)). + strtoupper(substr(date('l',$event['start']),0,2)); + break; + } + if ($event['recur_interval'] > 1) $rrule['INTERVAL'] = $event['recur_interval']; + if ($event['recur_enddate']) $rrule['UNTIL'] = date('Ymd',$event['recur_enddate']); // only day is set in eGW + + // no idea how to get the Horde parser to produce a standard conformant + // RRULE:FREQ=... (note the double colon after RRULE, we cant use the $parameter array) + // so we create one value manual ;-) + foreach($rrule as $name => $value) + { + $attributes['RRULE'][] = $name . '=' . $value; + } + $attributes['RRULE'] = implode(';',$attributes['RRULE']); + } + break; + + case 'EXDATE': + if ($event['recur_exception']) + { + $days = array(); + foreach($event['recur_exception'] as $day) + { + $days[] = date('Ymd',$day); + } + $attributes['EXDATE'] = implode(',',$days); + $parameters['EXDATE']['VALUE'] = 'DATE'; + } + break; + + case 'PRIORITY': + $attributes['PRIORITY'] = (int) $this->priority_egw2ical[$event['priority']]; + break; + + case 'TRANSP': + $attributes['TRANSP'] = $event['non_blocking'] ? 'TRANSPARENT' : 'OPAQUE'; + break; + + case 'CATEGORIES': + if ($event['category']) + { + $attributes['CATEGORIES'] = implode(',',$this->categories($event['category'],$nul)); + } + break; + default: + if ($event[$egwFieldInfo['dbName']]) // dont write empty fields + { + $attributes[$icalFieldName] = $event[$egwFieldInfo['dbName']]; + } + break; + } + } + } + $modified = $GLOBALS['egw']->contenthistory->getTSforAction($eventGUID,'modify'); + $created = $GLOBALS['egw']->contenthistory->getTSforAction($eventGUID,'add'); + if (!$created && !$modified) $created = $event['modified']; + if ($created) $attributes['CREATED'] = $created; + if (!$modified) $modified = $event['modified']; + if ($modified) $attributes['LAST-MODIFIED'] = $modified; + + foreach($event['alarm'] as $alarmID => $alarmData) + { + $attributes['DALARM'] = $vcal->_exportDateTime($alarmData['time']); + $attributes['AALARM'] = $vcal->_exportDateTime($alarmData['time']); + // lets take only the first alarm + break; + } + + $attributes['UID'] = $eventGUID; + + foreach($attributes as $key => $value) + { + foreach(is_array($value) ? $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) ? $parameters[$key][$valueID] : $parameters[$key], + $GLOBALS['egw']->translation->charset(),'UTF-8'); + //echo "$key:$valueID: value=$valueData, param=".print_r($paramDate,true)."\n"; + $vevent->setAttribute($key, $valueData, $paramData); + $options = array(); + if($key != 'RRULE' && preg_match('/([\000-\012\015\016\020-\037\075])/',$valueData)) + { + $options['ENCODING'] = 'QUOTED-PRINTABLE'; + } + if(preg_match('/([\177-\377])/',$valueData)) + { + $options['CHARSET'] = 'UTF-8'; + } + $vevent->setParameter($key, $options); + } + } + $vcal->addComponent($vevent); + } + //_debug_array($vcal->exportvCalendar()); + + return $vcal->exportvCalendar(); + } + + function importVCal($_vcalData, $cal_id=-1) + { + // our (patched) horde classes, do NOT unfold folded lines, which causes a lot trouble in the import + $_vcalData = preg_replace("/[\r\n]+ /",'',$_vcalData); + + $vcal = &new Horde_iCalendar; + if(!$vcal->parsevCalendar($_vcalData)) + { + return FALSE; + } + + if(!is_array($this->supportedFields)) + { + $this->setSupportedFields(); + } + //echo "supportedFields="; _debug_array($this->supportedFields); + + $Ok = false; // returning false, if file contains no components + foreach($vcal->getComponents() as $component) + { + if(is_a($component, 'Horde_iCalendar_vevent')) + { + $supportedFields = $this->supportedFields; + #$event = array('participants' => array()); + $event = array(); + $alarms = array(); + $vcardData = array('recur_type' => 0); + + // lets see what we can get from the vcard + foreach($component->_attributes as $attributes) + { + $attributes['value'] = $GLOBALS['egw']->translation->convert($attributes['value'],'UTF-8'); + //echo "$attributes[name] = '$attributes[value]'
\n"; + + switch($attributes['name']) + { + case 'AALARM': + case 'DALARM': + if (preg_match('/.*Z$/',$attributes['value'],$matches)) + { + $alarmTime = $vcal->_parseDateTime($attributes['value']); + $alarms[$alarmTime] = array( + 'time' => $alarmTime + ); + } + break; + case 'CLASS': + $vcardData['public'] = (int)(strtolower($attributes['value']) == 'public'); + break; + case 'DESCRIPTION': + $vcardData['description'] = $attributes['value']; + break; + case 'DTEND': + if(date('H:i:s',$attributes['value']) == '00:00:00') + $attributes['value']--; + $vcardData['end'] = $attributes['value']; + break; + case 'DTSTART': + $vcardData['start'] = $attributes['value']; + break; + case 'LOCATION': + $vcardData['location'] = $attributes['value']; + break; + case 'RRULE': + $recurence = $attributes['value']; + $type = preg_match('/FREQ=([^;: ]+)/i',$recurence,$matches) ? $matches[1] : $recurence{0}; + // vCard 2.0 values for all types + if (preg_match('/UNTIL=([0-9T]+)/',$recurence,$matches)) + { + $vcardData['recur_enddate'] = $vcal->_parseDateTime($matches[1]); + } + if (preg_match('/INTERVAL=([0-9]+)/',$recurence,$matches)) + { + $vcardData['recur_interval'] = (int) $matches[1]; + } + $vcardData['recur_data'] = 0; + switch($type) + { + case 'W': + case 'WEEKLY': + $days = array(); + if(preg_match('/W(\d+) (.*) (.*)/',$recurence, $recurenceMatches)) // 1.0 + { + $vcardData['recur_interval'] = $recurenceMatches[1]; + $days = explode(' ',trim($recurenceMatches[2])); + if($recurenceMatches[3] != '#0') + $vcardData['recur_enddate'] = $vcal->_parseDateTime($recurenceMatches[3]); + $recur_days = $this->recur_days_1_0; + } + elseif (preg_match('/BYDAY=([^;: ]+)/',$recurence,$recurenceMatches)) // 2.0 + { + $days = explode(',',$recurenceMatches[1]); + $recur_days = $this->recur_days; + } + if ($days) + { + foreach($recur_days as $id => $day) + { + if (in_array(strtoupper(substr($day,0,2)),$days)) + { + $vcardData['recur_data'] |= $id; + } + } + $vcardData['recur_type'] = MCAL_RECUR_WEEKLY; + } + break; + + case 'D': // 1.0 + if(!preg_match('/D(\d+) (.*)/',$recurence, $recurenceMatches)) break; + $vcardData['recur_interval'] = $recurenceMatches[1]; + if($recurenceMatches[2] != '#0') + $vcardData['recur_enddate'] = $vcal->_parseDateTime($recurenceMatches[2]); + // fall-through + case 'DAILY': // 2.0 + $vcardData['recur_type'] = MCAL_RECUR_DAILY; + break; + + case 'M': + if(preg_match('/MD(\d+) (.*)/',$recurence, $recurenceMatches)) + { + $vcardData['recur_type'] = MCAL_RECUR_MONTHLY_MDAY; + if($recurenceMatches[1] > 1) + $vcardData['recur_interval'] = $recurenceMatches[1]; + if($recurenceMatches[2] != '#0') + $vcardData['recur_enddate'] = $vcal->_parseDateTime($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($recurenceMatches[4] != '#0') + $vcardData['recur_enddate'] = $vcal->_parseDateTime($recurenceMatches[4]); + } + break; + case 'MONTHLY': + $vcardData['recur_type'] = strstr($recurence,'BYDAY') ? + MCAL_RECUR_MONTHLY_WDAY : MCAL_RECUR_MONTHLY_MDAY; + break; + + case 'Y': // 1.0 + if(!preg_match('/YM(\d+) (.*)/',$recurence, $recurenceMatches)) break; + $vcardData['recur_interval'] = $recurenceMatches[1]; + if($recurenceMatches[2] != '#0') + $vcardData['recur_enddate'] = $vcal->_parseDateTime($recurenceMatches[2]); + // fall-through + case 'YEARLY': // 2.0 + $vcardData['recur_type'] = MCAL_RECUR_YEARLY; + break; + } + break; + case 'EXDATE': + // ToDo: $vcardData['recur_exception'] = ... + break; + case 'SUMMARY': + $vcardData['title'] = $attributes['value']; + break; + case 'UID': + $event['uid'] = $vcardData['uid'] = $attributes['value']; + if ($cal_id <= 0 && !empty($vcardData['uid']) && ($uid_event = $this->read($vcardData['uid']))) + { + $event['id'] = $uid_event['id']; + unset($uid_event); + } + break; + case 'TRANSP': + $vcardData['non_blocking'] = $attributes['value'] == 'TRANSPARENT'; + break; + case 'PRIORITY': + $vcardData['priority'] = (int) $this->priority_ical2egw[$attributes['value']]; + break; + case 'CATEGORIES': + $vcardData['category'] = array(); + if ($attributes['value']) + { + if (!is_object($this->cat)) + { + if (!is_object($GLOBALS['egw']->categories)) + { + $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories',$this->owner,'calendar'); + } + $this->cat =& $GLOBALS['egw']->categories; + } + foreach(explode(',',$attributes['value']) as $cat_name) + { + if (!($cat_id = $this->cat->name2id($cat_name))) + { + $cat_id = $this->cat->add( array('name' => $cat_name,'descr' => $cat_name )); + } + $vcardData['category'][] = $cat_id; + } + } + break; + case 'ATTENDEE': + if (preg_match('/MAILTO:([@.a-z0-9_-]+)/i',$attributes['value'],$matches) && + ($uid = $GLOBALS['egw']->accounts->name2id($matches[1],'account_email'))) + { + $event['participants'][$uid] = isset($attributes['params']['PARTSTAT']) ? + $this->status_ical2egw[strtoupper($attributes['params']['PARTSTAT'])] : + ($uid == $event['owner'] ? 'A' : 'U'); + } + break; + case 'ORGANIZER': // will be written direct to the event + if (preg_match('/MAILTO:([@.a-z0-9_-]+)/i',$attributes['value'],$matches) && + ($uid = $GLOBALS['egw']->accounts->name2id($matches[1],'account_email'))) + { + $event['owner'] = $uid; + } + break; + case 'CREATED': // will be written direct to the event + if ($event['modified']) break; + // fall through + case 'LAST-MODIFIED': // will be written direct to the event + $event['modified'] = $attributes['value']; + break; + } + } + 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']) + ); + } + //echo "event=";_debug_array($vcardData); + + // now that we know what the vard provides, we merge that data with the information we have about the device + $event['priority'] = 2; + if($cal_id > 0) + { + $event['id'] = $cal_id; + } + while(($fieldName = array_shift($supportedFields))) + { + switch($fieldName) + { + case 'alarms': + // not handled here + break; + case 'recur_type': + $event['recur_type'] = $vcardData['recur_type']; + if ($event['recur_type'] != MCAL_RECUR_NONE) + { + foreach(array('recur_interval','recur_enddate','recur_data','recur_exception') as $r) + { + if(isset($vcardData[$r])) + { + $event[$r] = $vcardData[$r]; + } + } + } + unset($supportedFields['recur_type']); + unset($supportedFields['recur_interval']); + unset($supportedFields['recur_enddate']); + unset($supportedFields['recur_data']); + break; + default: + if (isset($vcardData[$fieldName])) + { + $event[$fieldName] = $vcardData[$fieldName]; + } + unset($supportedFields[$fieldName]); + break; + } + } + + // add ourself to new events as participant + if($cal_id == -1 && !isset($this->supportedFields['participants'])) + { + $event['participants'] = array($GLOBALS['egw_info']['user']['account_id'] => 'A'); + } + #foreach($event as $key => $value) + #{ + # error_log("KEY: $key VALUE: $value"); + #} + #echo "event=";_debug_array($event);exit; + + if (!($Ok = $this->update($event, TRUE))) { + break; // stop with the first error + } + else + { + $eventID =& $Ok; + + // handle the alarms + if(count($alarms) > 0 || (isset($this->supportedFields['alarms']) && count($alarms) == 0)) + { + // delete the old alarms + $updatedEvent = $this->read($eventID); + foreach($updatedEvent['alarm'] as $alarmID => $alarmData) + { + $this->delete_alarm($alarmID); + } + } + + foreach($alarms as $alarm) + { + $alarm['offset'] = $event['start'] - $alarm['time']; + $alarm['owner'] = $GLOBALS['egw_info']['user']['account_id']; + $this->save_alarm($eventID, $alarm); + } + } + } + } + return $Ok; + } + + function setSupportedFields($_productManufacturer='file', $_productName='') + { + $defaultFields = array('public' => 'public', 'description' => 'description', 'end' => 'end', + 'start' => 'start', 'location' => 'location', 'recur_type' => 'recur_type', + 'recur_interval' => 'recur_interval', 'recur_data' => 'recur_data', 'recur_enddate' => 'recur_enddate', + 'title' => 'title', 'priority' => 'priority', 'alarms' => 'alarms', + + ); + + switch(strtolower($_productManufacturer)) + { + case 'nexthaus corporation': + switch(strtolower($_productName)) + { + default: + # participants disabled until working correctly + #$this->supportedFields = $defaultFields + array('participants' => 'participants'); + $this->supportedFields = $defaultFields; + break; + } + break; + + // multisync does not provide anymore information then the manufacturer + // we suppose multisync with evolution + case 'the multisync project': + switch(strtolower($_productName)) + { + default: + $this->supportedFields = $defaultFields; + break; + } + break; + + case 'sonyericsson': + switch(strtolower($_productName)) + { + case 'd750i': + default: + $this->supportedFields = $defaultFields; + break; + } + break; + + case 'synthesis ag': + switch(strtolower($_productName)) + { + default: + $this->supportedFields = $defaultFields; + break; + } + break; + + case 'file': // used outside of SyncML, eg. by the calendar itself ==> all possible fields + $this->supportedFields = $defaultFields + array( + 'participants' => 'participants', + 'owner' => 'owner', + 'non_blocking' => 'non_blocking', + 'category' => 'category', + ); + break; + + // the fallback for SyncML + default: + error_log("Client not found: $_productManufacturer $_productName"); + $this->supportedFields = $defaultFields; + break; + } + } + + function icaltoegw($_vcalData) { + // our (patched) horde classes, do NOT unfold folded lines, which causes a lot trouble in the import + $_vcalData = preg_replace("/[\r\n]+ /",'',$_vcalData); + + $vcal = &new Horde_iCalendar; + if(!$vcal->parsevCalendar($_vcalData)) + { + return FALSE; + } + + if(!is_array($this->supportedFields)) + { + $this->setSupportedFields(); + } + //echo "supportedFields="; _debug_array($this->supportedFields); + + $Ok = false; // returning false, if file contains no components + foreach($vcal->getComponents() as $component) + { + if(is_a($component, 'Horde_iCalendar_vevent')) + { + $supportedFields = $this->supportedFields; + #$event = array('participants' => array()); + $event = array(); + $alarms = array(); + $vcardData = array('recur_type' => 0); + + // lets see what we can get from the vcard + foreach($component->_attributes as $attributes) + { + $attributes['value'] = $GLOBALS['egw']->translation->convert($attributes['value'],'UTF-8'); + //echo "$attributes[name] = '$attributes[value]'
\n"; + + switch($attributes['name']) + { + case 'AALARM': + case 'DALARM': + if (preg_match('/.*Z$/',$attributes['value'],$matches)) + { + $alarmTime = $vcal->_parseDateTime($attributes['value']); + $alarms[$alarmTime] = array( + 'time' => $alarmTime + ); + } + break; + case 'CLASS': + $vcardData['public'] = (int)(strtolower($attributes['value']) == 'public'); + break; + case 'DESCRIPTION': + $vcardData['description'] = $attributes['value']; + break; + case 'DTEND': + if(date('H:i:s',$attributes['value']) == '00:00:00') + $attributes['value']--; + $vcardData['end'] = $attributes['value']; + break; + case 'DTSTART': + $vcardData['start'] = $attributes['value']; + break; + case 'LOCATION': + $vcardData['location'] = $attributes['value']; + break; + case 'RRULE': + $recurence = $attributes['value']; + $type = preg_match('/FREQ=([^;: ]+)/i',$recurence,$matches) ? $matches[1] : $recurence{0}; + // vCard 2.0 values for all types + if (preg_match('/UNTIL=([0-9T]+)/',$recurence,$matches)) + { + $vcardData['recur_enddate'] = $vcal->_parseDateTime($matches[1]); + } + if (preg_match('/INTERVAL=([0-9]+)/',$recurence,$matches)) + { + $vcardData['recur_interval'] = (int) $matches[1]; + } + $vcardData['recur_data'] = 0; + switch($type) + { + case 'W': + case 'WEEKLY': + $days = array(); + if(preg_match('/W(\d+) (.*) (.*)/',$recurence, $recurenceMatches)) // 1.0 + { + $vcardData['recur_interval'] = $recurenceMatches[1]; + $days = explode(' ',trim($recurenceMatches[2])); + if($recurenceMatches[3] != '#0') + $vcardData['recur_enddate'] = $vcal->_parseDateTime($recurenceMatches[3]); + $recur_days = $this->recur_days_1_0; + } + elseif (preg_match('/BYDAY=([^;: ]+)/',$recurence,$recurenceMatches)) // 2.0 + { + $days = explode(',',$recurenceMatches[1]); + $recur_days = $this->recur_days; + } + if ($days) + { + foreach($recur_days as $id => $day) + { + if (in_array(strtoupper(substr($day,0,2)),$days)) + { + $vcardData['recur_data'] |= $id; + } + } + $vcardData['recur_type'] = MCAL_RECUR_WEEKLY; + } + break; + + case 'D': // 1.0 + if(!preg_match('/D(\d+) (.*)/',$recurence, $recurenceMatches)) break; + $vcardData['recur_interval'] = $recurenceMatches[1]; + if($recurenceMatches[2] != '#0') + $vcardData['recur_enddate'] = $vcal->_parseDateTime($recurenceMatches[2]); + // fall-through + case 'DAILY': // 2.0 + $vcardData['recur_type'] = MCAL_RECUR_DAILY; + break; + + case 'M': + if(preg_match('/MD(\d+) (.*)/',$recurence, $recurenceMatches)) + { + $vcardData['recur_type'] = MCAL_RECUR_MONTHLY_MDAY; + if($recurenceMatches[1] > 1) + $vcardData['recur_interval'] = $recurenceMatches[1]; + if($recurenceMatches[2] != '#0') + $vcardData['recur_enddate'] = $vcal->_parseDateTime($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($recurenceMatches[4] != '#0') + $vcardData['recur_enddate'] = $vcal->_parseDateTime($recurenceMatches[4]); + } + break; + case 'MONTHLY': + $vcardData['recur_type'] = strstr($recurence,'BYDAY') ? + MCAL_RECUR_MONTHLY_WDAY : MCAL_RECUR_MONTHLY_MDAY; + break; + + case 'Y': // 1.0 + if(!preg_match('/YM(\d+) (.*)/',$recurence, $recurenceMatches)) break; + $vcardData['recur_interval'] = $recurenceMatches[1]; + if($recurenceMatches[2] != '#0') + $vcardData['recur_enddate'] = $vcal->_parseDateTime($recurenceMatches[2]); + // fall-through + case 'YEARLY': // 2.0 + $vcardData['recur_type'] = MCAL_RECUR_YEARLY; + break; + } + break; + case 'EXDATE': + // ToDo: $vcardData['recur_exception'] = ... + break; + case 'SUMMARY': + $vcardData['title'] = $attributes['value']; + break; + case 'UID': + $event['uid'] = $vcardData['uid'] = $attributes['value']; + if ($cal_id <= 0 && !empty($vcardData['uid']) && ($uid_event = $this->read($vcardData['uid']))) + { + $event['id'] = $uid_event['id']; + unset($uid_event); + } + break; + case 'TRANSP': + $vcardData['non_blocking'] = $attributes['value'] == 'TRANSPARENT'; + break; + case 'PRIORITY': + $vcardData['priority'] = (int) $this->priority_ical2egw[$attributes['value']]; + break; + case 'CATEGORIES': + $vcardData['category'] = array(); + if ($attributes['value']) + { + if (!is_object($this->cat)) + { + if (!is_object($GLOBALS['egw']->categories)) + { + $GLOBALS['egw']->categories =& CreateObject('phpgwapi.categories',$this->owner,'calendar'); + } + $this->cat =& $GLOBALS['egw']->categories; + } + foreach(explode(',',$attributes['value']) as $cat_name) + { + if (!($cat_id = $this->cat->name2id($cat_name))) + { + $cat_id = $this->cat->add( array('name' => $cat_name,'descr' => $cat_name )); + } + $vcardData['category'][] = $cat_id; + } + } + break; + case 'ATTENDEE': + if (preg_match('/MAILTO:([@.a-z0-9_-]+)/i',$attributes['value'],$matches) && + ($uid = $GLOBALS['egw']->accounts->name2id($matches[1],'account_email'))) + { + $event['participants'][$uid] = isset($attributes['params']['PARTSTAT']) ? + $this->status_ical2egw[strtoupper($attributes['params']['PARTSTAT'])] : + ($uid == $event['owner'] ? 'A' : 'U'); + } + break; + case 'ORGANIZER': // will be written direct to the event + if (preg_match('/MAILTO:([@.a-z0-9_-]+)/i',$attributes['value'],$matches) && + ($uid = $GLOBALS['egw']->accounts->name2id($matches[1],'account_email'))) + { + $event['owner'] = $uid; + } + break; + case 'CREATED': // will be written direct to the event + if ($event['modified']) break; + // fall through + case 'LAST-MODIFIED': // will be written direct to the event + $event['modified'] = $attributes['value']; + break; + } + } + 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']) + ); + } + //echo "event=";_debug_array($vcardData); + + // now that we know what the vard provides, we merge that data with the information we have about the device + $event['priority'] = 2; + if($cal_id > 0) + { + $event['id'] = $cal_id; + } + while(($fieldName = array_shift($supportedFields))) + { + switch($fieldName) + { + case 'alarms': + // not handled here + break; + case 'recur_type': + $event['recur_type'] = $vcardData['recur_type']; + if ($event['recur_type'] != MCAL_RECUR_NONE) + { + foreach(array('recur_interval','recur_enddate','recur_data','recur_exception') as $r) + { + if(isset($vcardData[$r])) + { + $event[$r] = $vcardData[$r]; + } + } + } + unset($supportedFields['recur_type']); + unset($supportedFields['recur_interval']); + unset($supportedFields['recur_enddate']); + unset($supportedFields['recur_data']); + break; + default: + if (isset($vcardData[$fieldName])) + { + $event[$fieldName] = $vcardData[$fieldName]; + } + unset($supportedFields[$fieldName]); + break; + } + } + + return $event; + } + } + + return false; + } + + function search($_vcalData) { + if(!$event = $this->icaltoegw($_vcalData)) { + return false; + } + + $search['start'] = $event['start']; + $search['end'] = $event['end']; + + unset($event['description']); + unset($event['start']); + unset($event['end']); + + foreach($event as $key => $value) { + if (substr($key,0,6) != 'recur_') { + $search['query']['cal_'.$key] = $value; + } else { + #$search['query'][$key] = $value; + } + } + + if($foundEvents = parent::search($search)) { + if(is_array($foundEvents)) { + $event = array_shift($foundEvents); + return $event['id']; + } + } + + return false; + } + } +?> diff --git a/calendar/inc/class.sifcalendar.inc.php b/calendar/inc/class.sifcalendar.inc.php new file mode 100644 index 0000000000..b5e7351355 --- /dev/null +++ b/calendar/inc/class.sifcalendar.inc.php @@ -0,0 +1,503 @@ + * + * -------------------------------------------- * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; version 2 of the License. * + \**************************************************************************/ + + /* $Id$ */ + + require_once EGW_SERVER_ROOT.'/calendar/inc/class.bocalupdate.inc.php'; + require_once EGW_SERVER_ROOT.'/phpgwapi/inc/horde/Horde/iCalendar.php'; + + class sifcalendar extends bocalupdate + { + var $sifMapping = array( + 'Start' => 'start', + 'End' => 'end', + 'AllDayEvent' => 'alldayevent', + 'BillingInformation' => '', + 'Body' => 'description', + 'BusyStatus' => '', + 'Categories' => 'category', + 'Companies' => '', + 'Importance' => 'priority', + 'IsRecurring' => 'isrecurring', + 'Location' => 'location', + 'MeetingStatus' => '', + 'Mileage' => '', + 'ReminderMinutesBeforeStart' => 'reminderstart', + 'ReminderSet' => 'reminderset', + 'ReplyTime' => '', + 'Sensitivity' => 'public', + 'Subject' => 'title', + 'RecurrenceType' => 'recur_type', + 'Interval' => 'recur_interval', + 'MonthOfYear' => '', + 'DayOfMonth' => '', + 'DayOfWeekMask' => 'recur_weekmask', + 'Instance' => '', + 'PatternStartDate' => '', + 'NoEndDate' => 'recur_noenddate', + 'PatternEndDate' => 'recur_enddate', + 'Occurrences' => '', + ); + + // the calendar event array + var $event; + + // constants for recurence type + const olRecursDaily = 0; + const olRecursWeekly = 1; + const olRecursMonthly = 2; + const olRecursMonthNth = 3; + const olRecursYearly = 5; + 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; + + function startElement($_parser, $_tag, $_attributes) { + } + + function endElement($_parser, $_tag) { + #error_log($_tag .' => '. $this->sifData); + if(!empty($this->sifMapping[$_tag])) { + $this->event[$this->sifMapping[$_tag]] = $this->sifData; + } + unset($this->sifData); + } + + function characterData($_parser, $_data) { + $this->sifData .= $_data; + } + + function siftoegw($_sifdata) { + $vcal = &new Horde_iCalendar; + $finalEvent = array(); + $sysCharSet = $GLOBALS['egw']->translation->charset(); + $sifData = base64_decode($_sifdata); + #error_log($sifData); + + $tmpfname = tempnam('/tmp/sync/contents','sife_'); + + $handle = fopen($tmpfname, "w"); + fwrite($handle, $sifData); + fclose($handle); + + $this->xml_parser = xml_parser_create('UTF-8'); + xml_set_object($this->xml_parser, $this); + xml_parser_set_option($this->xml_parser, XML_OPTION_CASE_FOLDING, false); + xml_set_element_handler($this->xml_parser, "startElement", "endElement"); + xml_set_character_data_handler($this->xml_parser, "characterData"); + $this->strXmlData = xml_parse($this->xml_parser, $sifData); + if(!$this->strXmlData) { + error_log(sprintf("XML error: %s at line %d", + xml_error_string(xml_get_error_code($this->xml_parser)), + xml_get_current_line_number($this->xml_parser))); + return false; + } + #error_log(print_r($this->event, true)); + + foreach($this->event as $key => $value) { + $value = $GLOBALS['egw']->translation->convert($value, 'utf-8', $sysCharSet); + #error_log("$key => $value"); + switch($key) { + case 'alldayevent': + if($value == 1) { + $startParts = explode('-',$this->event['start']); + $finalEvent['start'] = mktime(0, 0, 0, $startParts[1], $startParts[2], $startParts[0]); + $finalEvent['end'] = mktime(23, 59, 59, $startParts[1], $startParts[2], $startParts[0]); + } + break; + + case 'public': + $finalEvent[$key] = ((int)$value > 0) ? 0 : 1; + break; + + case 'category': + if(!empty($value)) { + $isAdmin = $GLOBALS['egw']->acl->check('run',1,'admin'); + $egwCategories =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'calendar'); + $categories = explode('; ',$value); + $cat_id = ''; + foreach($categories as $categorieName) { + $categorieName = trim($categorieName); + if(!($cat_id = $egwCategories->name2id($categorieName)) && $isAdmin) { + $cat_id = $egwCategories->add(array('name' => $categorieName, 'descr' => $categorieName)); + } + if($cat_id) { + if(!empty($finalEvent[$key])) $finalEvent[$key] .= ','; + $finalEvent[$key] .= $cat_id; + } + } + } + break; + + case 'end': + case 'start': + if($this->event['alldayevent'] < 1) { + $finalEvent[$key] = $vcal->_parseDateTime($value); + } + break; + + case 'isrecurring': + if($value == 1) { + $finalEvent['recur_interval'] = $this->event['recur_interval']; + if($this->event['recur_noenddate'] == 0) { + $finalEvent['recur_enddate'] = $vcal->_parseDateTime($this->event['recur_enddate']); + } + switch($this->event['recur_type']) { + case self::olRecursDaily: + $finalEvent['recur_type'] = MCAL_RECUR_DAILY; + break; + + case self::olRecursWeekly: + $finalEvent['recur_type'] = MCAL_RECUR_WEEKLY; + $finalEvent['recur_data'] = $this->event['recur_weekmask']; + break; + + case self::olRecursMonthly: + $finalEvent['recur_type'] = MCAL_RECUR_MONTHLY_MDAY; + break; + + case self::olRecursMonthNth: + $finalEvent['recur_type'] = MCAL_RECUR_MONTHLY_WDAY; + break; + + case self::olRecursYearly: + $finalEvent['recur_type'] = MCAL_RECUR_YEARLY; + $finalEvent['recur_interval'] = 1; + break; + } + } + break; + + case 'priority': + $finalEvent[$key] = $value+1; + break; + + case 'reminderset': + if($value == 1) { + $finalEvent['alarm'] = $this->event['reminderstart']; + } + break; + + case 'recur_type': + case 'recur_enddate': + case 'recur_interval': + case 'recur_weekmask': + case 'reminderstart': + // do nothing, get's handled in isrecuring clause + break; + + default: + $finalEvent[$key] = $value; + break; + } + } + + #$middleName = ($finalEvent['n_middle']) ? ' '.trim($finalEvent['n_middle']) : ''; + #$finalEvent['fn'] = trim($finalEvent['n_given']. $middleName .' '. $finalEvent['n_family']); + + error_log(print_r($finalEvent, true)); + + + return $finalEvent; + } + + function search($_sifdata) { + if(!$event = $this->siftoegw($_sifdata)) { + return false; + } + + $search['start'] = $event['start']; + $search['end'] = $event['end']; + + unset($event['description']); + unset($event['start']); + unset($event['end']); + + foreach($event as $key => $value) { + if (substr($key,0,6) != 'recur_') { + $search['query']['cal_'.$key] = $value; + } else { + #$search['query'][$key] = $value; + } + } + + if($foundEvents = parent::search($search)) { + if(is_array($foundEvents)) { + $event = array_shift($foundEvents); + return $event['id']; + } + } + + return false; + } + + /** + * @return int contact id + * @param string $_vcard the vcard + * @param int $_abID the internal addressbook id + * @desc import a vard into addressbook + */ + function addSIF($_sifdata, $_calID) + { + $calID = false; + + #error_log('ABID: '.$_abID); + #error_log(base64_decode($_sifdata)); + + if(!$event = $this->siftoegw($_sifdata)) { + return false; + } + + if(isset($event['alarm'])) { + $alarm = $event['alarm']; + unset($event['alarm']); + } + + if($_calID > 0) + { + // update entry + $event['id'] = $_calID; + } + + if($eventID = $this->update($event, TRUE)) { + $updatedEvent = $this->read($eventID); + foreach($updatedEvent['alarm'] as $alarmID => $alarmData) + { + $this->delete_alarm($alarmID); + } + + if(isset($alarm)) { + $alarmData['time'] = $event['start'] - ($alarm*60); + $alarmData['offset'] = $alarm*60; + $alarmData['all'] = 1; + $alarmData['owner'] = $GLOBALS['egw_info']['user']['account_id']; + error_log(print_r($alarm, true)); + $this->save_alarm($eventID, $alarmData); + } + } + + return $eventID; + } + + /** + * return a vcard + * + * @param int $_id the id of the contact + * @param int $_vcardProfile profile id for mapping from vcard values to egw addressbook + * @return string containing the vcard + */ + function getSIF($_id) + { + $fields = array_unique(array_values($this->sifMapping)); + sort($fields); + + #$event = $this->read($_id,null,false,'server'); + #error_log("FOUND EVENT: ". print_r($event, true)); + + if($event = $this->read($_id,null,false,'server')) { + $sysCharSet = $GLOBALS['egw']->translation->charset(); + $vcal = &new Horde_iCalendar; + + + $sifEvent = ''; + + foreach($this->sifMapping as $sifField => $egwField) + { + if(empty($egwField)) continue; + + #error_log("$sifField => $egwField"); + #error_log('VALUE1: '.$event[$egwField]); + $value = $GLOBALS['egw']->translation->convert($event[$egwField], $sysCharSet, 'utf-8'); + #error_log('VALUE2: '.$value); + + switch($sifField) + { + case 'Categories': + if(!empty($value)) { + $egwCategories =& CreateObject('phpgwapi.categories',$GLOBALS['egw_info']['user']['account_id'],'calendar'); + $categories = explode(',',$value); + $value = ''; + foreach($categories as $cat_id) { + if($catData = $egwCategories->return_single($cat_id)) { + if(!empty($value)) $value .= '; '; + $value .= $catData[0]['name']; + } + } + } + $sifEvent .= "<$sifField>$value"; + break; + + case 'Importance': + $value = $value-1; + $sifEvent .= "<$sifField>$value"; + break; + + case 'RecurrenceType': + case 'Interval': + case 'PatternStartDate': + case 'NoEndDate': + case 'DayOfWeekMask': + case 'PatternEndDate': + break; + + case 'IsRecurring': + switch($event['recur_type']) { + case MCAL_RECUR_NONE: + $sifEvent .= "<$sifField>0"; + break; + + case MCAL_RECUR_DAILY: + $eventInterval = ($event['recur_interval'] > 1 ? $event['recur_interval'] : 1); + $recurStartDate = mktime(0,0,0,date('m',$event['start']), date('d', $event['start']), date('Y', $event['start'])); + + $sifEvent .= "<$sifField>1"; + $sifEvent .= ''. self::olRecursDaily .''; + $sifEvent .= ''. $eventInterval .''; + $sifEvent .= ''. $vcal->_exportDateTime($recurStartDate) .''; + if($event['recur_enddate'] == 0) { + $sifEvent .= '1'; + } else { + $recurEndDate = mktime(24,0,0,date('m',$event['recur_enddate']), date('d', $event['recur_enddate']), date('Y', $event['recur_enddate'])); + + $sifEvent .= '0'; + $sifEvent .= ''. $vcal->_exportDateTime($recurEndDate) .''; + $totalDays = ($recurEndDate - $recurStartDate) / 86400; + $occurrences = ceil($totalDays / $eventInterval); + $sifEvent .= ''. $occurrences .''; + } + break; + + case MCAL_RECUR_WEEKLY: + $eventInterval = ($event['recur_interval'] > 1 ? $event['recur_interval'] : 1); + $recurStartDate = mktime(0,0,0,date('m',$event['start']), date('d', $event['start']), date('Y', $event['start'])); + + $sifEvent .= "<$sifField>1"; + $sifEvent .= ''. self::olRecursWeekly .''; + $sifEvent .= ''. $eventInterval .''; + $sifEvent .= ''. $vcal->_exportDateTime($recurStartDate) .''; + $sifEvent .= ''. $event['recur_data'] .''; + if($event['recur_enddate'] == 0) { + $sifEvent .= '1'; + } else { + $recurEndDate = mktime(24, 0, 0, date('m',$event['recur_enddate']), date('d', $event['recur_enddate']), date('Y', $event['recur_enddate'])); + + $daysPerWeek = substr_count(decbin($event['recur_data']),'1'); + $sifEvent .= '0'; + $sifEvent .= ''. $vcal->_exportDateTime($recurEndDate) .''; + $totalWeeks = floor(($recurEndDate - $recurStartDate) / (86400*7)); + #error_log("AAA: $daysPerWeek $totalWeeks"); + $occurrences = ($totalWeeks / $eventInterval) * $daysPerWeek; + for($i = $recurEndDate; $i > $recurStartDate + ($totalWeeks * 86400*7); $i = $i - 86400) { + switch(date('w', $i-1)) { + case 0: + if($event['recur_data'] & 1) $occurrences++; + break; + // monday + case 1: + if($event['recur_data'] & 2) $occurrences++; + break; + case 2: + if($event['recur_data'] & 4) $occurrences++; + break; + case 3: + if($event['recur_data'] & 8) $occurrences++; + break; + case 4: + error_log(decbin($event['recur_data'])); + error_log(decbin(8)); + if($event['recur_data'] & 16) $occurrences++; + break; + case 5: + if($event['recur_data'] & 32) $occurrences++; + break; + case 6: + if($event['recur_data'] & 64) $occurrences++; + break; + } + } + $sifEvent .= ''. $occurrences .''; + } + break; + } + break; + + case 'Sensitivity': + $value = (!$value ? '2' : '0'); + $sifEvent .= "<$sifField>$value"; + break; + + case 'Folder': + # skip currently. This is the folder where Outlook stores the contact. + #$sifEvent .= "<$sifField>/"; + break; + + case 'AllDayEvent': + case 'End': + // get's handled by Start clause + break; + + case 'Start': + if($event['end'] - $event['start'] == 86399 && date('Y-m-d', $event['end']) == date('Y-m-d', $event['start'])) { + $value = date('Y-m-d'); + $sifEvent .= "$value"; + $sifEvent .= "$value"; + $sifEvent .= "1"; + } else { + $value = $vcal->_exportDateTime($event['start']); + $sifEvent .= "$value"; + $value = $vcal->_exportDateTime($event['end']); + $sifEvent .= "$value"; + $sifEvent .= "0"; + } + break; + + case 'ReminderMinutesBeforeStart': + break; + + case 'ReminderSet': + if(count((array)$event['alarm']) > 0) { + $sifEvent .= "<$sifField>1"; + foreach($event['alarm'] as $alarmID => $alarmData) + { + $sifEvent .= ''. $alarmData['offset']/60 .''; + // lets take only the first alarm + break; + } + } else { + $sifEvent .= "<$sifField>0"; + } + break; + + default: + $sifEvent .= "<$sifField>$value"; + break; + } + } + $sifEvent .= ''; + + return base64_encode($sifEvent); + } + + if($this->xmlrpc) + { + $GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['no_access'],$GLOBALS['xmlrpcstr']['no_access']); + } + return False; + } + + }