<?php
	/**************************************************************************\
	* eGroupWare - iCalendar Parser                                            *
	* http://www.egroupware.org                                                *
	* Written by Lars Kneschke <lkneschke@egroupware.org>                      *
	* --------------------------------------------                             *
	*  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: class.boical.inc.php 21797 2006-06-09 02:57:38Z lkneschke $ */

	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 <lkneschke@egroupware.org>
	 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
	 * @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) 
					{
						switch($attributes['name']) 
						{
							case 'AALARM':
							case 'DALARM':
								if (preg_match('/.*Z$/',$attributes['value'],$matches)) {
									$alarmTime = $vcal->_parseDateTime($attributes['value']);
									$alarms[$alarmTime] = array(
										'time' => $alarmTime
									);
								} elseif (preg_match('/(........T......);;;$/',$attributes['value'],$matches)) {
									#error_log(print_r($matches,true));
									$alarmTime = $vcal->_parseDateTime($matches[1]);
									$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');
	 				}

					#error_log('ALARMS');
					#error_log(print_r($alarms,true));
					
					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)
					{
						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_' && substr($key,0,5) != 'alarm') {
					$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;
		}
	}
?>
