3 * A Class for connecting to a caldav server
8 * @author Andrew McMillan <andrew@mcmillan.net.nz>
9 * @copyright Andrew McMillan
10 * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL version 3 or later
13 require_once('XMLDocument.php');
16 * A class for holding basic calendar information
20 public $url, $displayname, $getctag;
22 function __construct( $url, $displayname = null, $getctag = null ) {
24 $this->displayname
= $displayname;
25 $this->getctag
= $getctag;
28 function __toString() {
29 return( '(URL: '.$this->url
.' Ctag: '.$this->getctag
.' Displayname: '.$this->displayname
.')'. "\n" );
35 * A class for accessing DAViCal via CalDAV, as a client
41 * Server, username, password, calendar
45 protected $base_url, $user, $pass, $entry, $protocol, $server, $port;
48 * The principal-URL we're using
50 protected $principal_url;
53 * The calendar-URL we're using
55 protected $calendar_url;
58 * The calendar-home-set we're using
60 protected $calendar_home_set;
63 * The calendar_urls we have discovered
65 protected $calendar_urls;
68 * The useragent which is send to the caldav server
72 public $user_agent = 'DAViCalClient';
74 protected $headers = array();
76 protected $requestMethod = "GET";
77 protected $httpRequest = ""; // for debugging http headers sent
78 protected $xmlRequest = ""; // for debugging xml sent
79 protected $httpResponse = ""; // http headers received
80 protected $xmlResponse = ""; // xml received
82 protected $parser; // our XML parser object
85 * Constructor, initialises the class
87 * @param string $base_url The URL for the calendar server
88 * @param string $user The name of the user logging in
89 * @param string $pass The password for that user
91 function __construct( $base_url, $user, $pass ) {
94 $this->headers
= array();
96 if ( preg_match( '#^(https?)://([a-z0-9.-]+)(:([0-9]+))?(/.*)$#', $base_url, $matches ) ) {
97 $this->server
= $matches[2];
98 $this->base_url
= $matches[5];
99 if ( $matches[1] == 'https' ) {
100 $this->protocol
= 'ssl';
104 $this->protocol
= 'tcp';
107 if ( $matches[4] != '' ) {
108 $this->port
= intval($matches[4]);
112 trigger_error("Invalid URL: '".$base_url."'", E_USER_ERROR
);
117 * Adds an If-Match or If-None-Match header
119 * @param bool $match to Match or Not to Match, that is the question!
120 * @param string $etag The etag to match / not match against.
122 function SetMatch( $match, $etag = '*' ) {
123 $this->headers
['match'] = sprintf( "%s-Match: \"%s\"", ($match ?
"If" : "If-None"), trim($etag,'"'));
127 * Add a Depth: header. Valid values are 0, 1 or infinity
129 * @param int $depth The depth, default to infinity
131 function SetDepth( $depth = '0' ) {
132 $this->headers
['depth'] = 'Depth: '. ($depth == '1' ?
"1" : ($depth == 'infinity' ?
$depth : "0") );
136 * Add a Depth: header. Valid values are 1 or infinity
138 * @param int $depth The depth, default to infinity
140 function SetUserAgent( $user_agent = null ) {
141 if ( !isset($user_agent) ) $user_agent = $this->user_agent
;
142 $this->user_agent
= $user_agent;
146 * Add a Content-type: header.
148 * @param string $type The content type
150 function SetContentType( $type ) {
151 $this->headers
['content-type'] = "Content-type: $type";
155 * Set the calendar_url we will be using for a while.
157 * @param string $url The calendar_url
159 function SetCalendar( $url ) {
160 $this->calendar_url
= $url;
164 * Split response into httpResponse and xmlResponse
166 * @param string Response from server
168 function ParseResponse( $response ) {
169 $pos = strpos($response, '<?xml');
170 if ($pos === false) {
171 $this->httpResponse
= trim($response);
174 $this->httpResponse
= trim(substr($response, 0, $pos));
175 $this->xmlResponse
= trim(substr($response, $pos));
176 $this->xmlResponse
= preg_replace('{>[^>]*$}s', '>',$this->xmlResponse
);
177 $parser = xml_parser_create_ns('UTF-8');
178 xml_parser_set_option ( $parser, XML_OPTION_SKIP_WHITE
, 1 );
179 xml_parser_set_option ( $parser, XML_OPTION_CASE_FOLDING
, 0 );
181 if ( xml_parse_into_struct( $parser, $this->xmlResponse
, $this->xmlnodes
, $this->xmltags
) === 0 ) {
182 printf( "XML parsing error: %s - %s\n", xml_get_error_code($parser), xml_error_string(xml_get_error_code($parser)) );
183 // debug_print_backtrace();
184 // echo "\nNodes array............................................................\n"; print_r( $this->xmlnodes );
185 // echo "\nTags array............................................................\n"; print_r( $this->xmltags );
186 printf( "\nXML Reponse:\n%s\n", $this->xmlResponse
);
189 xml_parser_free($parser);
194 * Output http request headers
196 * @return HTTP headers
198 function GetHttpRequest() {
199 return $this->httpRequest
;
202 * Output http response headers
204 * @return HTTP headers
206 function GetResponseHeaders() {
207 return $this->httpResponseHeaders
;
210 * Output http response body
214 function GetResponseBody() {
215 return $this->httpResponseBody
;
222 function GetXmlRequest() {
223 return $this->xmlRequest
;
226 * Output xml response
230 function GetXmlResponse() {
231 return $this->xmlResponse
;
235 * Send a request to the server
237 * @param string $url The URL to make the request to
239 * @return string The content of the response from the server
241 function DoRequest( $url = null ) {
242 if(!defined("_FSOCK_TIMEOUT")){ define("_FSOCK_TIMEOUT", 10); }
245 if ( !isset($url) ) $url = $this->base_url
;
246 $this->request_url
= $url;
247 $url = preg_replace('{^https?://[^/]+}', '', $url);
248 // URLencode if it isn't already
249 if ( preg_match( '{[^%?&=+,.-_/a-z0-9]}', $url ) ) {
250 $url = str_replace(rawurlencode('/'),'/',rawurlencode($url));
251 $url = str_replace(rawurlencode('?'),'?',$url);
252 $url = str_replace(rawurlencode('&'),'&',$url);
253 $url = str_replace(rawurlencode('='),'=',$url);
254 $url = str_replace(rawurlencode('+'),'+',$url);
255 $url = str_replace(rawurlencode(','),',',$url);
257 $headers[] = $this->requestMethod
." ". $url . " HTTP/1.1";
258 $headers[] = "Authorization: Basic ".base64_encode($this->user
.":". $this->pass
);
259 $headers[] = "Host: ".$this->server
.":".$this->port
;
261 if ( !isset($this->headers
['content-type']) ) $this->headers
['content-type'] = "Content-type: text/plain";
262 foreach( $this->headers
as $ii => $head ) {
265 $headers[] = "Content-Length: " . strlen($this->body
);
266 $headers[] = "User-Agent: " . $this->user_agent
;
267 $headers[] = 'Connection: close';
268 $this->httpRequest
= join("\r\n",$headers);
269 $this->xmlRequest
= $this->body
;
271 $this->httpResponse
= '';
272 $this->xmlResponse
= '';
274 $fip = fsockopen( $this->protocol
. '://' . $this->server
, $this->port
, $errno, $errstr, _FSOCK_TIMEOUT
); //error handling?
275 if ( !(get_resource_type($fip) == 'stream') ) return false;
276 if ( !fwrite($fip, $this->httpRequest
."\r\n\r\n".$this->body
) ) { fclose($fip); return false; }
278 while( !feof($fip) ) { $response .= fgets($fip,8192); }
281 list( $this->httpResponseHeaders
, $this->httpResponseBody
) = preg_split( '{\r?\n\r?\n}s', $response, 2 );
282 if ( preg_match( '{Transfer-Encoding: chunked}i', $this->httpResponseHeaders
) ) $this->Unchunk();
284 $this->headers
= array(); // reset the headers array for our next request
285 $this->ParseResponse($this->httpResponseBody
);
291 * Unchunk a chunked response
295 $chunks = $this->httpResponseBody
;
296 // printf( "\n================================\n%s\n================================\n", $chunks );
299 if ( preg_match('{^((\r\n)?\s*([ 0-9a-fA-F]+)(;[^\n]*)?\r?\n)}', $chunks, $matches ) ) {
300 $octets = $matches[3];
301 $bytes = hexdec($octets);
302 $pos = strlen($matches[1]);
303 // printf( "Chunk size 0x%s (%d)\n", $octets, $bytes );
305 // printf( "---------------------------------\n%s\n---------------------------------\n", substr($chunks,$pos,$bytes) );
306 $content .= substr($chunks,$pos,$bytes);
307 $chunks = substr($chunks,$pos +
$bytes +
2);
308 // printf( "+++++++++++++++++++++++++++++++++\n%s\n+++++++++++++++++++++++++++++++++\n", $chunks );
316 $this->httpResponseBody
= $content;
317 // printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n%s\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n", $content );
322 * Send an OPTIONS request to the server
324 * @param string $url The URL to make the request to
326 * @return array The allowed options
328 function DoOptionsRequest( $url = null ) {
329 $this->requestMethod
= "OPTIONS";
331 $headers = $this->DoRequest($url);
332 $options_header = preg_replace( '/^.*Allow: ([a-z, ]+)\r?\n.*/is', '$1', $headers );
333 $options = array_flip( preg_split( '/[, ]+/', $options_header ));
340 * Send an XML request to the server (e.g. PROPFIND, REPORT, MKCALENDAR)
342 * @param string $method The method (PROPFIND, REPORT, etc) to use with the request
343 * @param string $xml The XML to send along with the request
344 * @param string $url The URL to make the request to
346 * @return array An array of the allowed methods
348 function DoXMLRequest( $request_method, $xml, $url = null ) {
350 $this->requestMethod
= $request_method;
351 $this->SetContentType("text/xml");
352 return $this->DoRequest($url);
358 * Get a single item from the server.
360 * @param string $url The URL to GET
362 function DoGETRequest( $url ) {
364 $this->requestMethod
= "GET";
365 return $this->DoRequest( $url );
370 * Get the HEAD of a single item from the server.
372 * @param string $url The URL to HEAD
374 function DoHEADRequest( $url ) {
376 $this->requestMethod
= "HEAD";
377 return $this->DoRequest( $url );
382 * PUT a text/icalendar resource, returning the etag
384 * @param string $url The URL to make the request to
385 * @param string $icalendar The iCalendar resource to send to the server
386 * @param string $etag The etag of an existing resource to be overwritten, or '*' for a new resource.
388 * @return string The content of the response from the server
390 function DoPUTRequest( $url, $icalendar, $etag = null ) {
391 $this->body
= $icalendar;
393 $this->requestMethod
= "PUT";
394 if ( $etag != null ) {
395 $this->SetMatch( ($etag != '*'), $etag );
397 $this->SetContentType('text/calendar; encoding="utf-8"');
398 $this->DoRequest($url);
401 if ( preg_match( '{^ETag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders
, $matches ) ) $etag = $matches[1];
402 if ( !isset($etag) ||
$etag == '' ) {
403 printf( "No etag in:\n%s\n", $this->httpResponseHeaders
);
404 $save_request = $this->httpRequest
;
405 $save_response_headers = $this->httpResponseHeaders
;
406 $this->DoHEADRequest( $url );
407 if ( preg_match( '{^Etag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders
, $matches ) ) $etag = $matches[1];
408 if ( !isset($etag) ||
$etag == '' ) {
409 printf( "Still No etag in:\n%s\n", $this->httpResponseHeaders
);
411 $this->httpRequest
= $save_request;
412 $this->httpResponseHeaders
= $save_response_headers;
419 * DELETE a text/icalendar resource
421 * @param string $url The URL to make the request to
422 * @param string $etag The etag of an existing resource to be deleted, or '*' for any resource at that URL.
424 * @return int The HTTP Result Code for the DELETE
426 function DoDELETERequest( $url, $etag = null ) {
429 $this->requestMethod
= "DELETE";
430 if ( $etag != null ) {
431 $this->SetMatch( true, $etag );
433 $this->DoRequest($url);
434 return $this->resultcode
;
439 * Get a single item from the server.
441 * @param string $url The URL to PROPFIND on
443 function DoPROPFINDRequest( $url, $props, $depth = 0 ) {
444 $this->SetDepth($depth);
445 $xml = new XMLDocument( array( 'DAV:' => '', 'urn:ietf:params:xml:ns:caldav' => 'C' ) );
446 $prop = new XMLElement('prop');
447 foreach( $props AS $v ) {
448 $xml->NSElement($prop,$v);
451 $this->body
= $xml->Render('propfind',$prop );
453 $this->requestMethod
= "PROPFIND";
454 $this->SetContentType("text/xml");
455 $this->DoRequest($url);
456 return $this->GetXmlResponse();
461 * Get/Set the Principal URL
463 * @param $url string The Principal URL to set
465 function PrincipalURL( $url = null ) {
467 $this->principal_url
= $url;
469 return $this->principal_url
;
474 * Get/Set the calendar-home-set URL
476 * @param $url array of string The calendar-home-set URLs to set
478 function CalendarHomeSet( $urls = null ) {
479 if ( isset($urls) ) {
480 if ( ! is_array($urls) ) $urls = array($urls);
481 $this->calendar_home_set
= $urls;
483 return $this->calendar_home_set
;
488 * Get/Set the calendar-home-set URL
490 * @param $urls array of string The calendar URLs to set
492 function CalendarUrls( $urls = null ) {
493 if ( isset($urls) ) {
494 if ( ! is_array($urls) ) $urls = array($urls);
495 $this->calendar_urls
= $urls;
497 return $this->calendar_urls
;
502 * Return the first occurrence of an href inside the named tag.
504 * @param string $tagname The tag name to find the href inside of
506 function HrefValueInside( $tagname ) {
507 foreach( $this->xmltags
[$tagname] AS $k => $v ) {
509 if ( $this->xmlnodes
[$j]['tag'] == 'DAV::href' ) {
510 return rawurldecode($this->xmlnodes
[$j]['value']);
518 * Return the href containing this property. Except only if it's inside a status != 200
520 * @param string $tagname The tag name of the property to find the href for
521 * @param integer $which Which instance of the tag should we use
523 function HrefForProp( $tagname, $i = 0 ) {
524 if ( isset($this->xmltags
[$tagname]) && isset($this->xmltags
[$tagname][$i]) ) {
525 $j = $this->xmltags
[$tagname][$i];
526 while( $j-- > 0 && $this->xmlnodes
[$j]['tag'] != 'DAV::href' ) {
527 // printf( "Node[$j]: %s\n", $this->xmlnodes[$j]['tag']);
528 if ( $this->xmlnodes
[$j]['tag'] == 'DAV::status' && $this->xmlnodes
[$j]['value'] != 'HTTP/1.1 200 OK' ) return null;
530 // printf( "Node[$j]: %s\n", $this->xmlnodes[$j]['tag']);
531 if ( $j > 0 && isset($this->xmlnodes
[$j]['value']) ) {
532 // printf( "Value[$j]: %s\n", $this->xmlnodes[$j]['value']);
533 return rawurldecode($this->xmlnodes
[$j]['value']);
537 printf( "xmltags[$tagname] or xmltags[$tagname][$i] is not set\n");
544 * Return the href which has a resourcetype of the specified type
546 * @param string $tagname The tag name of the resourcetype to find the href for
547 * @param integer $which Which instance of the tag should we use
549 function HrefForResourcetype( $tagname, $i = 0 ) {
550 if ( isset($this->xmltags
[$tagname]) && isset($this->xmltags
[$tagname][$i]) ) {
551 $j = $this->xmltags
[$tagname][$i];
552 while( $j-- > 0 && $this->xmlnodes
[$j]['tag'] != 'DAV::resourcetype' );
554 while( $j-- > 0 && $this->xmlnodes
[$j]['tag'] != 'DAV::href' );
555 if ( $j > 0 && isset($this->xmlnodes
[$j]['value']) ) {
556 return rawurldecode($this->xmlnodes
[$j]['value']);
565 * Return the <prop> ... </prop> of a propstat where the status is OK
567 * @param string $nodenum The node number in the xmlnodes which is the href
569 function GetOKProps( $nodenum ) {
571 $level = $this->xmlnodes
[$nodenum]['level'];
573 while ( $this->xmlnodes
[++
$nodenum]['level'] >= $level ) {
574 if ( $this->xmlnodes
[$nodenum]['tag'] == 'DAV::propstat' ) {
575 if ( $this->xmlnodes
[$nodenum]['type'] == 'open' ) {
580 if ( $status == 'HTTP/1.1 200 OK' ) break;
583 elseif ( !isset($this->xmlnodes
[$nodenum]) ||
!is_array($this->xmlnodes
[$nodenum]) ) {
586 elseif ( $this->xmlnodes
[$nodenum]['tag'] == 'DAV::status' ) {
587 $status = $this->xmlnodes
[$nodenum]['value'];
590 $props[] = $this->xmlnodes
[$nodenum];
598 * Attack the given URL in an attempt to find a principal URL
600 * @param string $url The URL to find the principal-URL from
602 function FindPrincipal( $url=null ) {
603 $xml = $this->DoPROPFINDRequest( $url, array('resourcetype', 'current-user-principal', 'owner', 'principal-URL',
604 'urn:ietf:params:xml:ns:caldav:calendar-home-set'), 1);
606 $principal_url = $this->HrefForProp('DAV::principal');
608 if ( !isset($principal_url) ) {
609 foreach( array('DAV::current-user-principal', 'DAV::principal-URL', 'DAV::owner') AS $href ) {
610 if ( !isset($principal_url) ) {
611 $principal_url = $this->HrefValueInside($href);
616 return $this->PrincipalURL($principal_url);
621 * Attack the given URL in an attempt to find a principal URL
623 * @param string $url The URL to find the calendar-home-set from
625 function FindCalendarHome( $recursed=false ) {
626 if ( !isset($this->principal_url
) ) {
627 $this->FindPrincipal();
630 $this->DoPROPFINDRequest( $this->principal_url
, array('urn:ietf:params:xml:ns:caldav:calendar-home-set'), 0);
633 $calendar_home = array();
634 foreach( $this->xmltags
['urn:ietf:params:xml:ns:caldav:calendar-home-set'] AS $k => $v ) {
635 if ( $this->xmlnodes
[$v]['type'] != 'open' ) continue;
636 while( $this->xmlnodes
[++
$v]['type'] != 'close' && $this->xmlnodes
[$v]['tag'] != 'urn:ietf:params:xml:ns:caldav:calendar-home-set' ) {
637 // printf( "Tag: '%s' = '%s'\n", $this->xmlnodes[$v]['tag'], $this->xmlnodes[$v]['value']);
638 if ( $this->xmlnodes
[$v]['tag'] == 'DAV::href' && isset($this->xmlnodes
[$v]['value']) )
639 $calendar_home[] = rawurldecode($this->xmlnodes
[$v]['value']);
643 if ( !$recursed && count($calendar_home) < 1 ) {
644 $calendar_home = $this->FindCalendarHome(true);
647 return $this->CalendarHomeSet($calendar_home);
652 * Find the calendars, from the calendar_home_set
654 function FindCalendars( $recursed=false ) {
655 if ( !isset($this->calendar_home_set
[0]) ) {
656 $this->FindCalendarHome();
658 $this->DoPROPFINDRequest( $this->calendar_home_set
[0], array('resourcetype','displayname','http://calendarserver.org/ns/:getctag'), 1);
660 $calendars = array();
661 if ( isset($this->xmltags
['urn:ietf:params:xml:ns:caldav:calendar']) ) {
662 $calendar_urls = array();
663 foreach( $this->xmltags
['urn:ietf:params:xml:ns:caldav:calendar'] AS $k => $v ) {
664 $calendar_urls[$this->HrefForProp('urn:ietf:params:xml:ns:caldav:calendar', $k)] = 1;
667 foreach( $this->xmltags
['DAV::href'] AS $i => $hnode ) {
668 $href = rawurldecode($this->xmlnodes
[$hnode]['value']);
670 if ( !isset($calendar_urls[$href]) ) continue;
672 // printf("Seems '%s' is a calendar.\n", $href );
674 $calendar = new CalendarInfo($href);
675 $ok_props = $this->GetOKProps($hnode);
676 foreach( $ok_props AS $v ) {
677 // printf("Looking at: %s[%s]\n", $href, $v['tag'] );
678 switch( $v['tag'] ) {
679 case 'http://calendarserver.org/ns/:getctag':
680 $calendar->getctag
= $v['value'];
682 case 'DAV::displayname':
683 $calendar->displayname
= $v['value'];
687 $calendars[] = $calendar;
691 return $this->CalendarUrls($calendars);
696 * Find the calendars, from the calendar_home_set
698 function GetCalendarDetails( $url = null ) {
699 if ( isset($url) ) $this->SetCalendar($url);
701 $calendar_properties = array( 'resourcetype', 'displayname', 'http://calendarserver.org/ns/:getctag', 'urn:ietf:params:xml:ns:caldav:calendar-timezone', 'supported-report-set' );
702 $this->DoPROPFINDRequest( $this->calendar_url
, $calendar_properties, 0);
704 $hnode = $this->xmltags
['DAV::href'][0];
705 $href = rawurldecode($this->xmlnodes
[$hnode]['value']);
707 $calendar = new CalendarInfo($href);
708 $ok_props = $this->GetOKProps($hnode);
709 foreach( $ok_props AS $k => $v ) {
710 $name = preg_replace( '{^.*:}', '', $v['tag'] );
711 if ( isset($v['value'] ) ) {
712 $calendar->{$name} = $v['value'];
715 printf( "Calendar property '%s' has no text content\n", $v['tag'] );
724 * Get all etags for a calendar
726 function GetCollectionETags( $url = null ) {
727 if ( isset($url) ) $this->SetCalendar($url);
729 $this->DoPROPFINDRequest( $this->calendar_url
, array('getetag'), 1);
732 if ( isset($this->xmltags
['DAV::getetag']) ) {
733 foreach( $this->xmltags
['DAV::getetag'] AS $k => $v ) {
734 $href = $this->HrefForProp('DAV::getetag', $k);
735 if ( isset($href) && isset($this->xmlnodes
[$v]['value']) ) $etags[$href] = $this->xmlnodes
[$v]['value'];
744 * Get a bunch of events for a calendar with a calendar-multiget report
746 function CalendarMultiget( $event_hrefs, $url = null ) {
748 if ( isset($url) ) $this->SetCalendar($url);
751 foreach( $event_hrefs AS $k => $href ) {
752 $href = str_replace( rawurlencode('/'),'/',rawurlencode($href));
753 $hrefs .= '<href>'.$href.'</href>';
755 $this->body
= <<<EOXML
756 <?xml version="1.0" encoding="utf-8" ?>
757 <C:calendar-multiget xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
758 <prop><getetag/><C:calendar-data/></prop>
760 </C:calendar-multiget>
763 $this->requestMethod
= "REPORT";
764 $this->SetContentType("text/xml");
765 $this->DoRequest( $this->calendar_url
);
768 if ( isset($this->xmltags
['urn:ietf:params:xml:ns:caldav:calendar-data']) ) {
769 foreach( $this->xmltags
['urn:ietf:params:xml:ns:caldav:calendar-data'] AS $k => $v ) {
770 $href = $this->HrefForProp('urn:ietf:params:xml:ns:caldav:calendar-data', $k);
771 // echo "Calendar-data:\n"; print_r($this->xmlnodes[$v]);
772 $events[$href] = $this->xmlnodes
[$v]['value'];
776 foreach( $event_hrefs AS $k => $href ) {
777 $this->DoGETRequest($href);
778 $events[$href] = $this->httpResponseBody
;
787 * Given XML for a calendar query, return an array of the events (/todos) in the
788 * response. Each event in the array will have a 'href', 'etag' and '$response_type'
789 * part, where the 'href' is relative to the calendar and the '$response_type' contains the
790 * definition of the calendar data in iCalendar format.
792 * @param string $filter XML fragment which is the <filter> element of a calendar-query
793 * @param string $url The URL of the calendar, or empty/null to use the 'current' calendar_url
795 * @return array An array of the relative URLs, etags, and events from the server. Each element of the array will
796 * be an array with 'href', 'etag' and 'data' elements, corresponding to the URL, the server-supplied
797 * etag (which only varies when the data changes) and the calendar data in iCalendar format.
799 function DoCalendarQuery( $filter, $url = '' ) {
801 if ( !empty($url) ) $this->SetCalendar($url);
803 $this->body
= <<<EOXML
804 <?xml version="1.0" encoding="utf-8" ?>
805 <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
813 $this->requestMethod
= "REPORT";
814 $this->SetContentType("text/xml");
815 $this->DoRequest( $this->calendar_url
);
818 foreach( $this->xmlnodes
as $k => $v ) {
819 switch( $v['tag'] ) {
820 case 'DAV::response':
821 if ( $v['type'] == 'open' ) {
824 elseif ( $v['type'] == 'close' ) {
825 $report[] = $response;
829 $response['href'] = basename( rawurldecode($v['value']) );
832 $response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
834 case 'urn:ietf:params:xml:ns:caldav:calendar-data':
835 $response['data'] = $v['value'];
844 * Get the events in a range from $start to $finish. The dates should be in the
845 * format yyyymmddThhmmssZ and should be in GMT. The events are returned as an
846 * array of event arrays. Each event array will have a 'href', 'etag' and 'event'
847 * part, where the 'href' is relative to the calendar and the event contains the
848 * definition of the event in iCalendar format.
850 * @param timestamp $start The start time for the period
851 * @param timestamp $finish The finish time for the period
852 * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
854 * @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
856 function GetEvents( $start = null, $finish = null, $relative_url = '' ) {
858 if ( isset($start) && isset($finish) )
859 $range = "<C:time-range start=\"$start\" end=\"$finish\"/>";
863 $filter = <<<EOFILTER
865 <C:comp-filter name="VCALENDAR">
866 <C:comp-filter name="VEVENT">
873 return $this->DoCalendarQuery($filter, $relative_url);
878 * Get the todo's in a range from $start to $finish. The dates should be in the
879 * format yyyymmddThhmmssZ and should be in GMT. The events are returned as an
880 * array of event arrays. Each event array will have a 'href', 'etag' and 'event'
881 * part, where the 'href' is relative to the calendar and the event contains the
882 * definition of the event in iCalendar format.
884 * @param timestamp $start The start time for the period
885 * @param timestamp $finish The finish time for the period
886 * @param boolean $completed Whether to include completed tasks
887 * @param boolean $cancelled Whether to include cancelled tasks
888 * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
890 * @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
892 function GetTodos( $start, $finish, $completed = false, $cancelled = false, $relative_url = "" ) {
894 if ( $start && $finish ) {
895 $time_range = <<<EOTIME
896 <C:time-range start="$start" end="$finish"/>
900 // Warning! May contain traces of double negatives...
901 $neg_cancelled = ( $cancelled === true ?
"no" : "yes" );
902 $neg_completed = ( $cancelled === true ?
"no" : "yes" );
904 $filter = <<<EOFILTER
906 <C:comp-filter name="VCALENDAR">
907 <C:comp-filter name="VTODO">
908 <C:prop-filter name="STATUS">
909 <C:text-match negate-condition="$neg_completed">COMPLETED</C:text-match>
911 <C:prop-filter name="STATUS">
912 <C:text-match negate-condition="$neg_cancelled">CANCELLED</C:text-match>
913 </C:prop-filter>$time_range
919 return $this->DoCalendarQuery($filter, $relative_url);
924 * Get the calendar entry by UID
927 * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
928 * @param string $component_type The component type inside the VCALENDAR. Default 'VEVENT'.
930 * @return array An array of the relative URL, etag, and calendar data returned from DoCalendarQuery() @see DoCalendarQuery()
932 function GetEntryByUid( $uid, $relative_url = '', $component_type = 'VEVENT' ) {
935 $filter = <<<EOFILTER
937 <C:comp-filter name="VCALENDAR">
938 <C:comp-filter name="$component_type">
939 <C:prop-filter name="UID">
940 <C:text-match icollation="i;octet">$uid</C:text-match>
948 return $this->DoCalendarQuery($filter, $relative_url);
953 * Get the calendar entry by HREF
955 * @param string $href The href from a call to GetEvents or GetTodos etc.
957 * @return string The iCalendar of the calendar entry
959 function GetEntryByHref( $href ) {
960 $href = str_replace( rawurlencode('/'),'/',rawurlencode($href));
961 return $this->DoGETRequest( $href );
969 * $cal = new CalDAVClient( "http://calendar.example.com/caldav.php/username/calendar/", "username", "password", "calendar" );
970 * $options = $cal->DoOptionsRequest();
971 * if ( isset($options["PROPFIND"]) ) {
972 * // Fetch some information about the events in that calendar
974 * $folder_xml = $cal->DoXMLRequest("PROPFIND", '<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><getcontentlength/><getcontenttype/><resourcetype/><getetag/></prop></propfind>' );
976 * // Fetch all events for February
977 * $events = $cal->GetEvents("20070101T000000Z","20070201T000000Z");
978 * foreach ( $events AS $k => $event ) {
979 * do_something_with_event_data( $event['data'] );
982 * $acc["google"] = array(
983 * "user"=>"kunsttherapie@gmail.com",
985 * "server"=>"ssl://www.google.com",
987 * "uri"=>"https://www.google.com/calendar/dav/kunsttherapie@gmail.com/events/",
990 * $acc["davical"] = array(
991 * "user"=>"some_user",
992 * "pass"=>"big secret",
993 * "server"=>"calendar.foo.bar",
995 * "uri"=>"http://calendar.foo.bar/caldav.php/some_user/home/",
997 * //*******************************
999 * $account = $acc["davical"];
1001 * //*******************************
1002 * $cal = new CalDAVClient( $account["uri"], $account["user"], $account["pass"], "", $account["server"], $account["port"] );
1003 * $options = $cal->DoOptionsRequest();
1004 * print_r($options);
1006 * //*******************************
1007 * //*******************************
1010 * <?xml version="1.0" encoding="utf-8" ?>
1011 * <D:propfind xmlns:D="DAV:" xmlns:C="http://calendarserver.org/ns/">
1015 * <D:resourcetype />
1020 * //if ( isset($options["PROPFIND"]) ) {
1021 * // Fetch some information about the events in that calendar
1022 * // $cal->SetDepth(1);
1023 * // $folder_xml = $cal->DoXMLRequest("PROPFIND", $xmlC);
1024 * // print_r( $folder_xml);
1027 * // Fetch all events for February
1028 * $events = $cal->GetEvents("20090201T000000Z","20090301T000000Z");
1029 * foreach ( $events as $k => $event ) {
1030 * print_r($event['data']);
1031 * print "\n---------------------------------------------\n";
1034 * //*******************************
1035 * //*******************************