<?php
/**
* a few helper functions for caldav client
*
* @package   caldav
* @subpackage   support
* @author Atmail
*/

/*
* Copyright (c) 2009-2010 ATMAIL. All rights reserved
* See http://atmail.com/license.php for license agreement
*/

// create_guid
// creates a new Globally Unique IDentifier
// marked as 'random' type
function create_guid()
{
	$rawid = strtoupper(md5(uniqid(rand(), true)));
	$workid = $rawid;

	// hopefully conform to the spec, mark this as a 'random'type
	// lets handle the version byte as a number
	$byte = hexdec( substr($workid,12,2) );
	$byte = $byte & hexdec("0f");
	$byte = $byte | hexdec("40");
	$workid = substr_replace($workid, strtoupper(dechex($byte)), 12, 2);

	// hopefully conform to the spec, mark this common variant
	// lets handle the variant
	$byte = hexdec( substr($workid,16,2) );
	$byte = $byte & hexdec("3f");
	$byte = $byte | hexdec("80");
	$workid = substr_replace($workid, strtoupper(dechex($byte)), 16, 2);

	// build a human readable version
	$rid = substr($rawid, 0, 8).'-'
	.substr($rawid, 8, 4).'-'
	.substr($rawid,12, 4).'-'
	.substr($rawid,16, 4).'-'
	.substr($rawid,20,12);

	// build a human readable version
	$wid = substr($workid, 0, 8).'-'
	.substr($workid, 8, 4).'-'
	.substr($workid,12, 4).'-'
	.substr($workid,16, 4).'-'
	.substr($workid,20,12);

	// lets return the 'random' type
	return $rid;
}

// ical_datetime
// converts a given unix timestamp to a format ical can understand
function ical_datetime($time_stamp, $zulu = false)
{

	if(gettype($time_stamp) == "string" && !is_numeric($time_stamp))
	{
    
		$time_stamp = strtotime($time_stamp);
	
	}
	if( $zulu !== false )
	{
		
		return gmdate("Ymd", $time_stamp) . "T" . gmdate("His", $time_stamp);
		
	}
	else
	{
		
		return date("Ymd", $time_stamp) . "T" . date("His", $time_stamp);
		
	}
	
}


function ical_encode($text)
{
	/*Value Name: TEXT
Purpose This value type is used to identify values that contain human
readable text.
Formal Definition: The character sets supported by this revision of
iCalendar are UTF-8 and US ASCII thereof. The applicability to other
character sets is for future work. The value type is defined by the
following notation.
text = *(TSAFE-CHAR / ":" / DQUOTE / ESCAPED-CHAR)
; Folded according to description above
ESCAPED-CHAR = "\\" / "\;" / "\," / "\N" / "\n")
; \\ encodes \, \N or \n encodes newline
; \; encodes ;, \, encodes ,
TSAFE-CHAR = %x20-21 / %x23-2B / %x2D-39 / %x3C-5B
%x5D-7E / NON-US-ASCII
; Any character except CTLs not needed by the current
; character set, DQUOTE, ";", ":", "\", ","
*/
	$text = stripslashes($text);

/*	for($a=0; $a < strlen($text); $a ++)
	{
		$char = $text[$a];
		$char_ord = ord($text[$a]);
	
		if( 	!in_array($char_ord, range(32, 33)) 
		&&	!in_array($char_ord, range(35, 43)) 
		&& 	!in_array($char_ord, range(45, 57))
		&&	!in_array($char_ord, range(60, 91))
		&&	!in_array($char_ord, range(93, 126))
		&&	!in_array($char, array(";", ",", "\n", ":", "\"")))
		{
			$text[$a] = ' ';
		}
	}*/
//	$text = iconv("ISO-8859-1", "UTF-8", $text);

	return str_replace(";", "\\;", 
		str_replace(",", "\\,",
		str_replace("\n", "\\n",
		str_replace("\N", "\\n",
		str_replace("\r", "", 
		str_replace("\\", "\\\\", 
		$text))))));
}

function ical_decode($text)
{

	$text = str_replace("\\\\", "\\", 
		str_replace("\\n", "\n",
		str_replace("\\,", ",", 
		str_replace("\\;", ";",
		$text))));
	return 	trim($text);//iconv("UTF-8", "ISO-8859-1", $text);

}

// ical_create_entry)
// processes a constructed array into a VCALENDAR entry
function ical_create_entry($array, $str='', $tzAtom = NULL)
{
	if( $tzAtom != NULL && isset($array['VTIMEZONE']))
	{
		// timezone information specifically given
		// so remove any old information
		unset($array['VTIMEZONE']);
	}
	
	$recurrence = '';
	foreach($array as $key=>$value){
		if(is_array($value)){
			if($key == 'VEVENT')
			{
				// we are about to start writing the event data
				// lets insert the raw timezone information if available
				if($tzAtom != NULL)
				{
					$str .= $tzAtom;
				}
			}
			$str .= "BEGIN:$key\n";
			
			$str = ical_create_entry($value, $str, $tzAtom);
			// newline has been trimmed from the above call
			// so, add another
			$str .= "\n";
			$str .= "END:$key\n";
		}
		else{
			if($key == "DESCRIPTION" || $key == "SUMMARY" || $key == "LOCATION")
			{
				$value = ical_encode($value);	
			}
			if($key == "RRULE2")
				$key = "RRULE";
				
			$str .= "$key:$value\n";
		}
	}

	// return the final string, but remove the newline
	return trim($str, "\n");
}

function seconds_to_ical_duration($duration)
{
	$converted_duration = 0;
	$sign = 1;

	if($duration < 0)
	{
		$duration_str = '-';
		$duration *= -1;
	}

	$weeks = (int)($duration / 7 / 24 / 60 / 60);
	$duration = $duration - ($weeks * 7 * 24 * 60 * 60);
	$days = (int)($duration / 24 / 60 / 60);
	$duration = $duration - ($days * 24 * 60 * 60);
	$hours = (int)($duration / 60 / 60);
	$duration = $duration - ($hours * 60 * 60);
	$minutes = (int)($duration / 60);
	$duration = $duration - ($minutes * 60);
	$seconds = (int)($duration);

	if($weeks || $days || $hours || $minutes || $seconds)
		$duration_str .= 'P';
	if($weeks)
		$duration_str .= $weeks . 'W';
	if($days)
		$duration_str .= $days . 'D';

	if($hours || $minutes || $seconds)
		$duration_str .= 'T';
	if($hours)
		$duration_str .= $hours . 'H';
	if($minutes)
		$duration_str .= $minutes . 'M';
	if($seconds)
		$duration_str .= $seconds . 'S';
	
	return $duration_str;
}

function ical_duration_to_seconds($duration)
{
	$converted_duration = 0;
	$sign = 1;

	if($duration[0] == '-')
	{
		$duration = substr($duration, 1, strlen($duration) - 1);
		$sign = -1;
	}
	else if ($duration[0] == '+')
	{
		$duration = substr($duration, 1, strlen($duration) - 1);
	}

	if($duration[0] == 'P')
	{
		/*dur-value  = (["+"] / "-") "P" (dur-date / dur-time / dur-week)
		dur-date   = dur-day [dur-time]
		dur-time   = "T" (dur-hour / dur-minute / dur-second)
		dur-week   = 1*DIGIT "W"
		dur-hour   = 1*DIGIT "H" [dur-minute]
		dur-minute = 1*DIGIT "M" [dur-second]
		dur-second = 1*DIGIT "S"
		dur-day    = 1*DIGIT "D"*/

		list($throw_away, $duration) = explode('P', $duration);

		$continue = true;
		$time_mode = false;
		while(strlen($duration) > 0)
		{
			if($duration[0] == 'T')
			{
				list($throw_away, $duration) = explode('T', $duration);
				$time_mode = true;
			}

			$letters = preg_split('/^[0-9][0-9]*/', $duration);
			$numbers = preg_split('/[A-Z]/', $duration);
			$numbers = $numbers[0];
			$letters = $letters[1][0];

			if($time_mode == true)
			{
				switch($letters)
				{
					case 'H' :
					// hour
					$converted_duration += $numbers * 60 * 60;
					break;
					case 'M' :
					// min
					$converted_duration += $numbers * 60;
					break;
					case 'S' :
					// second
					$converted_duration += $numbers;
					break;
				}
			}
			else
			{
				switch($letters)
				{
					case 'W' :
					// weeks
					$converted_duration += $numbers * 7 * 24 * 60 * 60;
					break;
					case 'D' :
					$converted_duration += $numbers * 24 * 60 * 60;
					// days
					break;
				}
			}
			// consume what we used
			$duration = substr($duration, strlen($numbers) + strlen($letters), strlen($duration));
		}
	}
	return $converted_duration * $sign;
}

// ical_load_entry
// processes a VCALENDAR entry into an array
function ical_load_entry($event)
{
	$debug = 0;
	$event_properties = array();
	// ok lets parse the event data
	// it will be presented to us in iCalendar file format
	// so lets explode it till its all nice and easy
	$event_lines = explode("\n", $event['data']);
	$multi_line_pos = 1;
	
	for($a=0; $a<count($event_lines); $a++)
	{
		if( isset($event_lines[$a][0])  )
		{
			if( $event_lines[$a][0] == "\t" || $event_lines[$a][0] == "\r" || $event_lines[$a][0] == "\n" || $event_lines[$a][0] == " ")
			{
				$event_lines[$a] = substr($event_lines[$a], 1);
				$event_lines[$a - $multi_line_pos] = trim( $event_lines[$a - $multi_line_pos], "\r\n");
				$event_lines[$a - $multi_line_pos] = $event_lines[$a - $multi_line_pos] . $event_lines[$a];
				$event_lines[$a] = "";
				$multi_line_pos ++;
			}
			else
			{
				$multi_line_pos = 1;	
			}
		}
		else
		{
			$multi_line_pos = 1;	
		}
	}
		
	$endseen = TRUE;
	$multi_prop_used = FALSE;
	$atom_array = array();
	$end_seen = array();
	$block_props = array();
	foreach ( $event_lines as $l => $line)
	{
		if($line != "" && $line != "\n" && $line != "\r\n" && $line != "\n\r")
		{
			$props = explode(":", $line, 2);
			
			if($props[0] != "")
			{
				if($props[0] == "BEGIN")
				{
					$end_seen[$props[1]] = false;				
					$atom_array[] = $props[1];
					if(!$endseen)
					{
						if(isset($curr_block_props))
						{
							$curr_array_hash = trim($curr_array_hash, "\x00..\x1F");
							$event_properties[$curr_array_hash] = $curr_block_props;						
						}
						$curr_block_props = array();
					}
					// new container
					$curr_array_hash = $props[1];
					
					$endseen = FALSE;
				}
				else if ($props[0] != "END")
				{
					if($props[1] != null)
					{
						$attribute = trim($props[0], "\x00..\x1F");
						$value = trim($props[1], "\x00..\x1F");
					
						if(strstr($attribute, ";") != null)
						{
							list($attribute, $junk) = explode(";", $attribute, 2);
						}
						if($attribute != "METHOD")
						{
							if(!isset($curr_block_props[$attribute]))
							{
								$curr_block_props[$attribute] = $value;
							}
							else
							{
								$curr_block_props[$attribute . '2'] = $value;
							}
						}
					}
				}
				else
				{
					//end tag detected, or unhandled tag
					$end_seen[$props[1]] = true;
					if(!isset($event_properties[$curr_array_hash]) || $end_seen[$curr_array_hash] == false)
					{
						$endseen = TRUE;
						// must be at the end
						$curr_array_hash = trim($curr_array_hash, "\x00..\x1F");
						
						if( (!array_key_exists($curr_array_hash, $end_seen) || $end_seen[$curr_array_hash] == false) && array_key_exists($curr_array_hash, $event_properties) )
						{
							$curr_block_props = array_merge($curr_block_props, $event_properties[$curr_array_hash]);
							unset($event_properties[$curr_array_hash]);
						}
	
						if( !array_key_exists($curr_array_hash,$event_properties) && count($curr_block_props) )
						{
							$event_properties[$curr_array_hash] = $curr_block_props;
						}
						else if( count($curr_block_props) )
						{
							$event_properties[$curr_array_hash . '2'] = $curr_block_props;
						}
					}
					else if (isset($event_properties[$curr_array_hash]) && $end_seen[$props[1]] == true)
					{
						$event_properties[$curr_array_hash] = array_merge($event_properties[$curr_array_hash], $curr_block_props);
					}
					$curr_block_props = array();
					array_pop($atom_array);
					
					if(count($atom_array) - 1 >= 0 && $atom_array[ count($atom_array) - 1 ] != '')
					{
						$curr_array_hash = $atom_array[count($atom_array) - 1];
						
						if(isset($event_properties[$curr_array_hash]))
						{						
							$curr_block_props = $event_properties[$curr_array_hash];
						}
					}
				}
			}
		}
	}
	// now, lets do some array merges
	if(isset($event_properties['STANDARD']))
	{
		$event_properties['VTIMEZONE']['STANDARD'] = (array) $event_properties['STANDARD'];
		unset($event_properties['STANDARD']);
	}

	if(isset($event_properties['DAYLIGHT']))
	{
		$event_properties['VTIMEZONE']['DAYLIGHT'] = (array) $event_properties['DAYLIGHT'];
		unset($event_properties['DAYLIGHT']);
	}

	if(isset($event_properties['VEVENT']))
	{
		$event_properties['VCALENDAR']['VEVENT'] = (array) $event_properties['VEVENT'];
		unset($event_properties['VEVENT']);
	}

	if(isset($event_properties['VTIMEZONE']))
	{
		$event_properties['VCALENDAR']['VTIMEZONE'] = (array) $event_properties['VTIMEZONE'];
		unset($event_properties['VTIMEZONE']);
	}

	if(isset($event_properties['VTODO']))
	{
		$event_properties['VCALENDAR']['VTODO'] = (array) $event_properties['VTODO'];
		unset($event_properties['VTODO']);
	}

	$event_properties['etag'] = isset($event['etag']) ? $event['etag'] : '';
	
	return $event_properties;
}

function finduserprops($cal, $relative_href)
{
	$options = $cal->DoOptionsRequest();
	$xml = null;
	if ( isset($options["PROPFIND"]) ) {
		// Fetch some information about the events in that calendar
		$cal->SetDepth(1);

$xml = <<<EOXML
<?xml version="1.0" encoding="utf-8"?>
<x0:propfind xmlns:x2="http://calendarserver.org/ns/" xmlns:x1="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:">
 <x0:prop>
  <x1:calendar-home-set/>
  <x1:calendar-user-address-set/>
  <x1:schedule-inbox-URL/>
  <x1:schedule-outbox-URL/>
  <x2:dropbox-home-URL/>
  <x2:notifications-URL/>
  <x0:displayname/>
 </x0:prop>
</x0:propfind>
EOXML;
		$xml = $cal->DoXMLRequest("PROPFIND", $xml, $relative_href);
	}
	
	return $xml;	
	
}

function getproxy($cal, $user, $relative_href) 
{
	$user_guid = str_replace("@", "_", $user);
	$user_principal = '/principals/__uids__/' . $user_guid . '/calendar-proxy-write/';

	$base_url = $cal->base_url;
	$cal->base_url = $user_principal;
	
$xml = <<<EOXML
<?xml version='1.0' encoding='utf-8'?>
<ns0:propfind xmlns:ns0="DAV:">
  <ns0:prop>
    <ns0:group-member-set />
    <ns0:group-membership />
  </ns0:prop>
</ns0:propfind>
EOXML;
	$xml = $cal->DoXMLRequest( "PROPFIND", $xml, $relative_href);
	if(strstr($xml, "HTML/1.1 501 Not Implemented") != NULL)
	{
		// not implemented !
		// this server does not support proxies..
		return array();
	}
	$cal->base_url = $base_url;
	
	$data = new DOMDocument();
	list($temp, $xml) = explode('<multistatus', $xml);
	$xml = "<multistatus" . $xml;		
	$user_principals = array();

	$data->loadXML($xml, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NSCLEAN);
	$members = $data->getElementsByTagName( "group-member-set" );
	if(!$members)
		return null;
	
	$members = $members->item(0);
	if($members)
	{
		$members = $members->getElementsByTagName ( "href");
	
		for($x=0;$x<$members->length; $x++)
		{
			list($temp, $username) = explode('/principals/__uids__/', $members->item($x)->nodeValue);
			$username = trim($username,'/');
			$username[strrpos($username,"_")] = "@";
			$proxy_users[] = array($username, "RW", $members->item($x)->nodeValue);
		}
	}
	
	return $proxy_users;
}

function addproxy($cal, $user, $pass, $proxy_user, $perms, $skipdupcheck = false)
{
	$user_guid = str_replace("@", "_", $user);
	$user_principal = '/principals/__uids__/' . $user_guid . '/calendar-proxy-write/';
	$proxy_user_guid = str_replace("@", "_", $proxy_user);
	$proxy_user_principal = '/principals/__uids__/' . $proxy_user_guid . '/';

	$caldav_server = new CalDAVClient( 'http://' . $cal->server . ':' . $cal->port . $user_principal, $user, $pass, 'calendar');
	$caldav_server->SetUserAgent("Atmail");
	
	$proxy_str = "";
	$duplicate = false;

	if($skipdupcheck == false)
	{
		$current_proxies = getproxy($cal, $user, '');
	
		if($current_proxies)
		{
			foreach($current_proxies as $current_proxy)
			{
				if($current_proxy[2] == $proxy_user_principal)
					$duplicate = true;
				
				$proxy_str .= '<x0:href>' . $current_proxy[2] . '</x0:href>';
			}
		}
		if($duplicate)
		{
			return false;		
		}
	}
	
	$proxy_str .= '<x0:href>' . $proxy_user_principal . '</x0:href>';

	$result = proppatch($caldav_server, '', 'group-member-set', $proxy_str);
	
	return $result;
}

function removeproxy($cal, $user, $pass, $proxy_user, $perms)
{
	$user_guid = str_replace("@", "_", $user);
	$user_principal = '/principals/__uids__/' . $user_guid . '/calendar-proxy-write/';
	$proxy_user_guid = str_replace("@", "_", $proxy_user);
	$proxy_user_principal = '/principals/__uids__/' . $proxy_user_guid . '/';

	$caldav_server = new CalDAVClient( 'http://' . $cal->server . ':' . $cal->port . $user_principal, $user, $pass, 'calendar');
	$caldav_server->SetUserAgent("Atmail");
	
	$current_proxies = getproxy($cal, $user, '');
	
	$proxy_str = "";
	$found = false;
	if( $current_proxies )
	{
		foreach($current_proxies as $current_proxy)
		{
			if( $proxy_user_principal == $current_proxy[2])
			{
				$found = true;
			}
			$proxy_str .= '<x0:href>' . $current_proxy[2] . '</x0:href>';
		}
	}
	
	if( !$found )
	{
		return false;
	}
	
	$proxy_str = str_replace('<x0:href>' . $proxy_user_principal . '</x0:href>', '', $proxy_str);
	$result = proppatch($caldav_server, '', 'group-member-set', $proxy_str);

	return $result;
}

function proppatch($cal, $relative_href, $prop, $patch)
{
$xml = <<<EOXML
<?xml version="1.0" encoding="UTF-8" ?>
<x0:propertyupdate xmlns:x0="DAV:"><x0:set><x0:prop><x0:$prop>$patch</x0:$prop></x0:prop></x0:set></x0:propertyupdate>
EOXML;
	return ($cal->DoXMLRequest( "PROPPATCH", $xml, $relative_href));
}

function get_caller_method()
{
    $traces = debug_backtrace();

    if (isset($traces[2]))
    {
        return $traces[2]['function'];
    }

    return null;
} 

function findprops($cal, $relative_href)
{
//	$options = $cal->DoOptionsRequest();
	$xml = null;
//	if ( isset($options["PROPFIND"]) ) 
{
		// Fetch some information about the events in that calendar
		$cal->SetDepth(1);

$xml = <<<EOXML
<?xml version="1.0" encoding="utf-8"?>
<x0:propfind xmlns:x1="http://calendarserver.org/ns/" xmlns:x0="DAV:" xmlns:x3="http://apple.com/ns/ical/" xmlns:x2="urn:ietf:params:xml:ns:caldav">
 <x0:prop>
  <x1:getctag/>
  <x0:getetag/>
  <x0:displayname/>
  <x2:calendar-description/>
  <x3:calendar-color/>
  <x3:calendar-order/>
  <x0:resourcetype/>
  <x2:calendar-free-busy-set/>
 </x0:prop>
</x0:propfind>
EOXML;
		$xml = $cal->DoXMLRequest("PROPFIND", $xml, $relative_href);
	}

	return $xml;	
}

function findetag($cal, $relative_href)
{
	return 0;
	$xml = null;
	Zend_Registry::get('log')->debug( "\n" . print_r($cal, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$cal \n");
	if( empty($cal) )
		return;
	// Fetch some information about the events in that calendar
	$cal->SetDepth(0);
	

$xml = <<<EOXML
<?xml version="1.0" encoding="utf-8"?>
<x0:propfind xmlns:x0="DAV:">
 <x0:prop>
  <x0:getetag/>
  <x0:getctag/>
 </x0:prop>
</x0:propfind>
EOXML;
		$xml = $cal->DoXMLRequest("PROPFIND", $xml, $relative_href);

	if(strpos($xml, '<getetag>') != false)
	{
		list($junk, $etag) = explode('<getetag>', $xml, 2);
		list($etag, $junk) = explode('</getetag>', $etag, 2);
		$etag = trim($etag, 'W/');
		$etag = trim($etag, '"');
	}
	else if(strpos($xml, '<getctag>') != false)
	{
		list($junk, $etag) = explode('<getctag>', $xml, 2);
		list($etag, $junk) = explode('</getctag>', $etag, 2);
		$etag = trim($etag, 'W/');
		$etag = trim($etag, '"');
	}
	return $etag;	
}

function getPrincipalMembers($cal, $username)
{
	$xml = null;

	// Fetch some information about the events in that calendar
	$cal->SetDepth(1);

$xml = <<<EOXML
<?xml version="1.0" encoding="utf-8"?>
<x0:propfind xmlns:x0="DAV:">
 <x0:prop>
  <x0:group-membership/>
 </x0:prop>
</x0:propfind>
EOXML;
	$oldbase = $cal->base_url;
	$cal->base_url = "/principals/users/" . $username . '/';

	$principals = array();
	
	$xml = $cal->DoXMLRequest("PROPFIND", $xml, '');
	$cal->base_url = $oldbase;
	
	if($xml != null)
	{
		$data = new DOMDocument();
		list($temp, $xml) = explode('<multistatus', $xml);
		$xml = "<multistatus" . $xml;		
		$folders = array();
			
		$data->loadXML($xml, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NSCLEAN);
		$responses = $data->getElementsByTagName( "response" );
		foreach($responses as $response)
		{	
			$resourcetype = $response->getElementsByTagName( "group-membership" )->item(0)->childNodes;
			$resource = array();
			for($x=0;$x<$resourcetype->length; $x++)
			{
				if(get_class($resourcetype->item($x)) == "DOMElement")
				{
					$current_principal = str_replace("calendar-proxy-write/", "", str_replace("calendar-proxy-read/", "", $resourcetype->item($x)->nodeValue));
										
					$oldbase = $cal->base_url;
					$cal->base_url = $current_principal;
					
				$xml2 = <<<EOXML
<?xml version="1.0" encoding="utf-8"?>
<x0:propfind xmlns:x0="DAV:" xmlns:x2="urn:ietf:params:xml:ns:caldav">
 <x0:prop>
  <x2:calendar-home-set/>
 </x0:prop>
</x0:propfind>
EOXML;

					$xml2 = $cal->DoXMLRequest("PROPFIND", $xml2, '');
					$cal->base_url = $oldbase;					
					$data2 = new DOMDocument();
					list($temp, $xml2) = explode('<multistatus', $xml2);
					$xml2 = "<multistatus" . $xml2;

					$data2->loadXML($xml2, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NSCLEAN);
					$responses = $data2->getElementsByTagName( "calendar-home-set" );

					foreach($responses as $response)
					{
						$test = trim($response->nodeValue);
						if($test != '')
							$calendar_home = $test . '/';
					}
					
					$principals[] = array($current_principal, $calendar_home);
				}
			}
		}
	}
	
	$folders = array();
	
	foreach($principals as $principal)
	{
		$url = 'http://' . $cal->server . ':' . $cal->port . '/calendars/users/';
		$oldbase = $cal->base_url;
		$principal[1] = str_replace("__uids__", "users", str_replace('/calendar/', '', $principal[1]));

		for($z=strlen($principal[1]);$z>0; $z--)
		{
			if($principal[1][$z-1] == '_')
			{
				$principal[1][$z-1] = '@';
				break;
			}
		}

		$cal->base_url = $principal[1];
		$folders = array_merge($folders, getfolders($cal, $url, 'calendar', ''));
		$cal->base_url = $oldbase;
	}
	
	return $folders;
}


function findacl($cal, $relative_href)
{
	// Fetch some information about the events in that calendar
	$cal->SetDepth(1);
	
$xml = <<<EOXML
<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:">
<D:prop>
<D:owner/>
<D:supported-privilege-set/>
<D:current-user-privilege-set/>
<D:acl/>
</D:prop>
</D:propfind>
EOXML;
	$xml = $cal->DoXMLRequest("PROPFIND", $xml, $relative_href);
	
	return $xml;	
}

function findfolders($cal, $type, $relative_href)
{

	$folders = null;
	$xml = findprops($cal, $relative_href);

	if($xml != null)
	{
		$data = new DOMDocument();
		if(strstr($xml, '<?xml version="1.0" encoding="utf-8"?>') != null)
		{
			list($temp, $xml) = explode('<?xml version="1.0" encoding="utf-8"?>', $xml);
			$xml = trim($xml, "\n");
		}
		else if(strstr($xml, '<multistatus') != null)
		{
			list($temp, $xml) = explode('<multistatus', $xml);
			$xml = '<multistatus' . $xml;
		}
		else if(strstr($xml, '<d:multistatus') != null)
		{
			list($temp, $xml) = explode('<d:multistatus', $xml);
			$xml = '<d:multistatus' . $xml;
		}
		$folders = array();
		$data->loadXML($xml, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NSCLEAN);
		$responses = $data->getElementsByTagName( "response" );
		foreach($responses as $response)
		{	
			$resourcetype = $response->getElementsByTagName( "resourcetype" )->item(0)->childNodes;
			$resource = array();
			for($x=0;$x<$resourcetype->length; $x++)
			{
				if(get_class($resourcetype->item($x)) == "DOMElement")
				{
					$tagName = $resourcetype->item($x)->tagName;
					$tags = explode(" :", $tagName);

					if(count($tags)==1)
						$tagName = $tags[0];
					else
						$tagName = $tags[1];
					
					$tags = explode(":", $tagName);

					if(count($tags)==1)
						$tagName = $tags[0];
					else
						$tagName = $tags[1];
						
						
					switch($tagName)
					{
						case "collection" :
							$resource[] = "collection";
						break;
						case "calendar" :
							$resource[] = "calendar";
						break;
						case "schedule-calendar" :
							$resource[] = "schedule-calendar";
						break;
						case "schedule-inbox" :
							$resource = Array("inbox");
						break;
						case "schedule-outbox" :
							$resource = Array("outbox");
						break;
						case "principal" :
							$resource[] = "principal";
						break;
						default:
							$resource[] = "unknown";
						break;
					}
				}
			}
			if(in_array($type, $resource) == true && in_array('unknown', $resource) == false )
			{
				$folder = array();
				$folder['href'] = $response->getElementsByTagName( "href" )->item(0)->nodeValue;
				$folder['ctag'] = $response->getElementsByTagName( "getctag" )->item(0)->nodeValue;
				$folder['displayname'] = $response->getElementsByTagName( "displayname")->item(0)->nodeValue;
				$folder['etag'] = str_replace('W/', '', str_replace('"', '', $response->getElementsByTagName( "getetag" )->item(0)->nodeValue));
				
				$description = $response->getElementsByTagName( "calendar-description" );
				if($description->length != 0)
					$folder['description'] = $description->item(0)->nodeValue;
				
				$color = $response->getElementsByTagName( "calendar-color" );
				if($color->length != 0)
					$folder['color'] = $color->item(0)->nodeValue;
				
				$order = $response->getElementsByTagName( "calendar-order" );
				if($order->length != 0)
					$folder['order'] = $order->item(0)->nodeValue;			

				$folder['resourcetype'] = $resource;
				$folders[] = $folder;
			}
		}
	}
	return $folders;
}

function extracturlinfo($url)
{
	list($temp, $url) = explode( "http://", $url);

	list($httpurl, $temp) = explode( "/", $url);

	$url = explode( "/", $url);
	$final = "";
	for($a=1;$a<count($url);$a++)
	{
		$final .= "/" . $url[$a];
	}
	$url = array($httpurl, $final);
	return $url;
}

function search_username($cal, $url, $username)
{
	$folders = getfolders($cal, $url, 'collection', '');
	$found = false;
	for($a=0;$a<count($folders);$a++)
	{
		if(strstr($folders[$a]['href'], $username)!= null)
		{
			$found = true;
			$a = count($folders);
		}
	}
	
	return $found;
}

function getfolders($cal, $url, $type, $relative_href = '')
{
	$folders = findfolders($cal, $type, $relative_href);

	$url_info = extracturlinfo($url);

	for($a=0;$a<count($folders);$a++)
	{
		if($folders[$a]['href'] == $url_info[1])
		{
			unset($folders[$a]);
		}
	}
	
	if($folders)
		foreach($folders as &$folder)
		{
			$folder['url_info'] = $url_info;
			if($url_info[1] && $folder['href'])
				$folder['relative_href'] = explode($url_info[1], $folder['href']);
			if(count($folder['relative_href']) == 1)
			{
				$folder['relative_href'] = $folder['relative_href'][0];
			}
			else
			{
				$folder['relative_href'] = $folder['relative_href'][1];
			}
			
			$folder['ctag'] = trim($folder['ctag'], '"');
			$folder['etag'] = trim($folder['etag'], '"');
			
			if($folder['etag'] == '')
				$folder['etag'] = $folder['ctag'];
		}
	
	return $folders;
}

//
//	This function will generate all dates for any recurring
//	entries within a date range and populate an array with
//	these values.
//
//	Variables :
//		startDateText/endDateText = (hopefully) ISO compat Date/Time
//									(%Y-%m-%d %H:%M)
//			-	They define the range of the results from
//				the recurrence search.
//
//	Returns :
//		array('ID' => $id, 'DateStart' => $recurrenceDateStart, 'DateEnd' => $recurrenceDateEnd)
/*****************************************************/
function ical_get_recurrences($start, $end, $entry) {


    // some variable inits
    $res = array();
    $patternFinished = 0;
    
    // ok, we are passed in malformed dates .
    // NOT ISO.
    // so, lets make a HACK.
    // argh.

    // START DATE
    $startDate = new Date($start);
    $startDate->setTZByID("UTC");
//	$startDate->setYear($start['year']);
//		$startDate->setMonth($start['month']);
///		$startDate->setDay($start['day']);
    $startDate->setHour(0);
    $startDate->setMinute(0);
    $startDate->setSecond(0);

    // END DATE
    $endDate = new Date($end);
    $endDate->setTZByID("UTC");
//	$endDate->setYear($end['year']);
//		$endDate->setMonth($end['month']);
//			$endDate->setDay($end['day']);
    $endDate->setHour(0);
    $endDate->setMinute(0);
    $endDate->setSecond(0);

    /* 
    [id] => 90A0E7EF-B719-0484-4D48-DF2FDBDC1320
    [DateStartCal] => 2010-03-29
    [DateStartTime] => 11:00:00
    [DateEndCal] => 2010-03-29
    [DateEndTime] => 13:15:00
    [Title] => MY NEW EVENT
    [ctag] => 2010-03-20 11:07:30.295741
    [etag] => 383DCB-1000-4BA411C2
    [calendar_owner] => test@centos.virgin
    [calendar] => calendar
    [relative_href] => test%40centos.virgin/calendar/
    [Importance] => 1
    [DateAlertPeriod] => 0
    [Availability] => Free
    [Type] => 2
    [AllDayEvent] => 0
    [IsRecurring] => 1
    [recurrencePattern] => 1
    [recurrenceTime] => 0
    [CalMessage] => 
    [Location] => 
    [DateModified] => 2010-03-29 05:29:42
    */
                
    $calenderEntrys = array($entry);
    foreach($calenderEntrys as $calenderEntry)
    {
        $calDateStart = new Date($calenderEntry['DateStartCal'] . ' ' . $calenderEntry['DateStartTime']);
        $calDateEnd = new Date($calenderEntry['DateEndCal'] . ' ' . $calenderEntry['DateEndTime']);
                        
        $calDateStart->setTZByID("UTC");
        $calDateEnd->setTZByID("UTC");

        $IsRecurring = $calenderEntry['IsRecurring'];
        //$IsException = $calenderEntry['IsException'];
        
        if($IsRecurring != 1)
        {
            // this is NOT a recurring entry.
            continue;
        }

        /*if($shared)
            $recurrencePattern = $sql->sqlhash("select * from recurrencePatterns where linkid = $id");
        else
            $recurrencePattern = $sql->sqlhash("select * from ecurrencePatterns where linkid = $id and account = \"$account\"");*/
        
//				$recurrenceArray = $this->ical2RecurrenceArray($calenderEntry['Data'], $calendarEntry['dtstart'], $calendarEntry['dtend']);
        $recurrenceArray = $calenderEntry['reccurenceArray']; //$this->RecurrenceArray2RecurrencePattern($recurrenceArray);

        $recurrenceType = $recurrenceArray['RecurrenceType'];
        $DayOfWeekMask = $recurrenceArray['DayOfWeekMask'];
        $DayOfMonth	= $recurrenceArray['DayOfMonth'];
        $Occurances = $recurrenceArray['Occurences'];
        $Duration = strtotime($calDateEnd->getDate()) - strtotime($calDateStart->getDate());
        $IsSimple = $recurrenceArray['IsSimple'];

        $Instance = $recurrenceArray['Instance'];
        if($Instance==32)
        {
            // special value - target the end of the month
            $targetEndOfMonth = 1;
        }
        else
        {
            $targetEndOfMonth = 0;
        }
        $skipInterval = $recurrenceArray['skipInterval'];
        $MonthOfYear = $recurrenceArray['MonthOfYear'];

        $patternStartDate = new Date($recurrenceArray['PatternStartDate']);
        $patternStartDate->setTZByID("UTC");
        
        $nextDate = new Date($patternStartDate);
        $nextDate->setTZByID("UTC");
//				$nextDate->subtractSpan	(new Date_span(13824000));
//				$nextDate->subtractSpan	(new Date_span(86400));

        $nextDate->setHour		($calDateStart->getHour());
        $nextDate->setMinute	($calDateStart->getMinute());
        $nextDate->setSecond	($calDateStart->getSecond());

        $id = $recurrenceId =( ((array_key_exists('etag', $calenderEntry) && $calenderEntry['etag'] != ''))?$calenderEntry['etag']:((array_key_exists('Data', $calenderEntry) && array_key_exists('etag', $calenderEntry['Data']) && $calenderEntry['Data']['etag'] != '')?$calenderEntry['Data']['etag']:$calenderEntry['id']));

        // grab any recurrence exceptions..
        $calenderExceptions = array(); //$sql->sqlmultihash("select * from $calTable where UserTo = \"$account\" and IsException = 1");
        $hasExceptions = 0; //count($calenderExceptions);

        $NoEndDate = $recurrenceArray['NoEndDate'];

        // do some setup and sanity
        if ($NoEndDate==0)
        {
            // the pattern has an end date !
            $patternEndDate = new Date($recurrenceArray['PatternEndDate']);
            $patternEndDate->setTZByID("UTC");
        }

        if ($Occurances!=0)
        {
            $Occurance_ON=1;
        }

        switch($recurrenceType)
        {

            case 0x0a : //Daily
            {

                // ****
                //	DayOfWeekMask
                //	
                //	Bit pattern as follows:
                //
                //	1 - Sun
                //	2 - Mon
                //	4 - Tues
                //	8 - Wends
                //	16 - Thurs
                //	32 - Fri
                //	64 - Sat
                //
                //	0x3E - All week days
                //	0x7F - All days
                // ****

                // do some type specific setup
                if($skipInterval != 0)
                {
                    // all days included in search
                    $DayOfWeekMask = 0x7F;
                }
                else
                {
                    // no skip interval information... must be weekdays only
                    $DayOfWeekMask = 0x3E;
                    $skipInterval = 1;
                }
            } break;
            case 0x0b : //weekly
            {
                if ($IsSimple)
                {
                    $DayOfWeekMask = 1 << $nextDate->getDayOfWeek();
                }
            } break;
            case 0x0c : //monthly
            case 0x0d : //yearly
            {
                // we want to wind forward till we hit the day of month
                if($DayOfMonth != 0)
                {
                    // day of month enabled

                    while($nextDate->getDay() != $DayOfMonth)
                        $nextDate = $nextDate->getNextDay();

                    if($MonthOfYear != 0)
                    {
                        // we have a month of the year to look at...
                        // so lets short circuit the loop
                        // and jump straight forward to the first blip
                        $nextDate->setMonth($MonthOfYear);
                    }

                    // we want all days included
                    $DayOfWeekMask = 0x7F;
                }
                else
                {
                    // we are nth month Nth instance of week mask
                    // so, lets reset the month

                    // short circuit the loop
                    if($MonthOfYear != 0)
                    {
                        // we have a month of the year to look at...
                        // so lets short circuit the loop
                        // and jump straight forward to the first blip
                        $nextDate->setMonth($MonthOfYear);
                    }

                    $nextDate->setDay(1);
                }

                // we found it ! ( or we span to death... )
            } break;
        }

        $totalInstances = 0;
        $totalLimit = 0;

        // loop through all the days within this range
        // oh, and protect the loop from spinning to death.
        while(Date::compare($nextDate, $endDate) != 1 && $totalLimit != 999)
        {
            $totalLimit++;

            if($targetEndOfMonth==1)
            {
                // we are targetting the end of the month
                // so lets set the instance counter to the total days in this month
                $Instance = $nextDate->getDaysInMonth();
            }

            $dayBits    = 1<<$nextDate->getDayOfWeek();
            $daytext = date("D", $nextDate->getTime());
            $dayEnabled = $DayOfWeekMask & $dayBits;

            if($NoEndDate == 0 && Date::compare($nextDate, $patternEndDate) >= 0)
            {
                // ok, we have hit a pattern end date...
                $patternFinished = 1;
                break;
            }

            /*

            if($hasExceptions)
            {
                foreach($calenderExceptions as $calenderException)
                {
                    $DateStart	= new Date($calenderException['DateStart']);
                    $DateEnd	= new Date($calenderException['DateEnd']);

                    if( Date::compare($nextDate, $DateStart) == 1 && Date::compare($nextDate, $DateEnd) == -1 )
                    {
                        $exceptId = $calenderException['id'];
                        $recurrenceExceptions = $sql->sqlmultihash("select * from recurrenceExceptions where exceptId = $exceptId and account = \"$account\"");


                    }
                }

                // lets check for any exceptions that might disable this day
                foreach ($recurrenceExceptions as $recurrenceException)
                {
                    if(Date::compare($nextDate,
                }
            }*/

            if ($dayEnabled)
            {						
                // check we are doing a modulas of zero !!!!!
                if($Instance == 0)
                    $Instance = 1;

                $totalInstances ++;

                if(Date::compare($nextDate, $patternStartDate) == 1 && ($dayEnabled != 0))
                {
                    if(($totalInstances % $Instance)==0)
                    {
                        // add the result!
                        $newEndDate = new Date($nextDate);
                        $newEndDate->setTZByID("UTC");
                        //if($NoEndDate == 1)
                        //	$Duration --;
                        $newEndDate->addSpan(new Date_Span(intval($Duration)));
                        
                        // test if this recurrence is actually the real date time of the original cal
                        if(Date::compare($nextDate, $calDateStart) != 0 && Date::compare($newEndDate, $calDateEnd) != 0)
                        {	
                            // AAAnnnnd, make sure we are within the date range specified !
                            if(Date::compare($nextDate, $startDate) >= 0 && Date::compare($newEndDate, $endDate) <= 0)
                            {
                                // store for cache here, under generated md5 string.
                                $DateStart = new Date($nextDate);
                                $DateStart->setTZByID("UTC");
                                
                                array_push($res, array('id' => $id, 'DateEnd' => $newEndDate, 'DateStart' => $DateStart));
                            }

                            if ($patternFinished==1)
                            {
                                return $res;
                            }

                            if($Occurances)
                            {
                                // test the total occurances...
                                $Occurances --;

                                if($Occurances <= 1)
                                {
                                    // we have finished !
                                    break;
                                }
                            }
                        }
                    }
                }
            }

            switch($recurrenceType)
            {
                case 0x0a : // Daily
                    $nextDate->addspan(new Date_Span($skipInterval * 60 * 60 * 24));
                break;
                case 0x0b : // weekly
                {
                    // jump forward a day..
                    $nextDate->addspan(new Date_Span(1 * 60 * 60 * 24));

                    // only start jump once we have got to the start of the interval pattern
                    if($nextDate->getDayOfWeek() == 0 && Date::compare($nextDate, $calDateStart) != -1 && $skipInterval != 1)
                    {
                        // we are at the start of a new week !
                        // lets add the interval now...
                        $nextDate->addspan( new Date_Span(($skipInterval -1)* 60 * 60 * 24 * 7));
                        //exit;
                    }
                }
                break;
                case 0x0c : // Monthly
                case 0x0d : // Yearly
                {
                    if($DayOfMonth!=0)
                    {
                        if($MonthOfYear == 0)
                        {
                            $currentMonth = $nextDate->getMonth();
                            $addInterval = 1;
                            $testDate = new Date($calDateStart);
                            $testDate->setTZByID("UTC");
                            $testDate->setDay($nextDate->getDay());
                            if(Date::compare($nextDate, $testDate) != -1)
                            {
                                $addInterval = $skipInterval;
                                if($addInterval == 0)
                                {
                                    $addInterval = 1;
                                }
                            }

                            if($currentMonth+$addInterval >= 13)
                            {
                                // erm, we walked off the end of the calender
                                $currentYear = $nextDate->getYear();
                                $nextDate->setYear($currentYear + 1);
                                $currentMonth = 0;
                            }

                            $nextDate->setMonth($currentMonth+$addInterval);
                        }
                        else
                        {
                            // erm, yeah.
                            // we are NOT targeting a specific month.... i.e. we are yearly !
                            // lets just add to the year
                            $nextDate->setYear($nextDate->getYear() + 1);
                        }
                    }
                    else
                    {
                        // we need to test days... we have a dayofweekmask !
                        if($totalInstances != 0 && ($totalInstances % $Instance)==0)
                        {

                            if($MonthOfYear == 0)
                            {
                                /*$currentMonth = $nextDate->getMonth();
                                if($currentMonth+1 == 13)
                                {
                                    // erm, we walked off the end of the calender
                                    $currentYear = $nextDate->getYear();
                                    $nextDate->setYear($currentYear + 1);
                                    $currentMonth = 0;
                                }

                                $nextDate->setMonth($currentMonth+1);*/
                            }
                            else
                            {
                                // erm, yeah.
                                // we are targeting a specific month.... i.e. we are yearly !
                                // lets just add to the year
                                $nextDate->setYear($nextDate->getYear() + 1);
                            }



                            // we have found the instance we want... move to the next month
                            $currentMonth = $nextDate->getMonth();
                            $addInterval = 1;
                            $testDate = new Date($calDateStart);
                            $testDate->setTZByID("UTC");
                            $testDate->setDay($nextDate->getDay());
                            if(Date::compare($nextDate, $testDate) != -1)
                            {
                                $addInterval = $skipInterval;
                                if($addInterval == 0)
                                {
                                    $addInterval = 1;
                                }
                            }

                            if($currentMonth+$addInterval >= 13)
                            {
                                // erm, we walked off the end of the calender
                                $currentYear = $nextDate->getYear();
                                $nextDate->setYear($currentYear + 1);
                                $currentMonth = 0;
                            }

                            $nextDate->setMonth($currentMonth+$addInterval);

                            $nextDate->setDay(1);			// erm, start from the start of the month thanks.
                            $totalInstances = 0;			// reset the instances!
                        }
                        else
                        {
                            $nextDate->addspan(new Date_Span(1 * 60 * 60 * 24));
                        }
                    }
                }
                break;
            }
        } // while(Date::compare($nextDate, $endDate) != 1)

        // hmm, did we loop to death ?
        if($totalLimit == 999)
        {
            // do something useful here
        }

    } // foreach($calenderEntrys as $calenderEntry)
    
    return $res;

}


function caldav_extract_alarm($event, $href)
{
    $alarm = array();

    if(isset($event['VALARM']))
    {
        $valarm = $event['VALARM'];
        $alarm['id'] = $event['VCALENDAR']['VEVENT']['UID'];
        $alarm['href'] = $href;
        
        // ensure action/trigger properties
        if($valarm['ACTION'] && $valarm['TRIGGER'])
        {				
            switch($valarm['ACTION'])
            {
                case 'DISPLAY' :
                {
                    $alarm['action'] = 'DISPLAY';
                    $alarm['description'] = $valarm['DESCRIPTION'];
                } break;
                case 'EMAIL' :
                {
                    $alarm['action'] = 'EMAIL';
                    $alarm['description'] = $valarm['DESCRIPTION'];
                    $alarm['attendee'] = str_replace('mailto:', '' , $valarm['ATTENDEE']);
                } break;
                
                case 'PROCEDURE':
                case 'AUDIO' :
                default:
                {
                    // not currently supported, ignore.	
                    $alarm['action'] = 'UNSUPPORTED';
                } break;
            }
            
            $alarm['eventtime'] = calendar::caldav_extract_dtstamps($event);
            if ( $valarm['TRIGGER'][strlen($valarm['TRIGGER'])-1] == 'Z')
            {
                // date	
                // utc
                $alarm['triggertype'] = "Date";
                
                $alarm['trigger'] = new Date($valarm['TRIGGER']);
                $alarm['alarmtime'] = $alarm['trigger'];
            }
            else
            {
                $alarm['triggertype'] = "Time";
                
                $alarm['trigger'] = ical_duration_to_seconds($valarm['TRIGGER']);
                $alarm['alarmtime'] = new Date($alarm['eventtime'][0]);
                $alarm['alarmtime']->addSpan(new Date_Span($alarm['trigger']));
            }
        }
    }

    return $alarm;
}
