DAViCal
caldav-client-v2.php
1 <?php
13 require_once('XMLDocument.php');
14 
19 class CalendarInfo {
20  public $url;
21  public $displayname;
22  public $getctag;
23 
24  function __construct( $url, $displayname = null, $getctag = null ) {
25  $this->url = $url;
26  $this->displayname = $displayname;
27  $this->getctag = $getctag;
28  }
29 
30  function __toString() {
31  return( '(URL: '.$this->url.' Ctag: '.$this->getctag.' Displayname: '.$this->displayname .')'. "\n" );
32  }
33 }
34 
35 if(!defined("_FSOCK_TIMEOUT")){
36  define("_FSOCK_TIMEOUT", 10);
37 }
38 
44 class CalDAVClient {
50  protected $base_url, $user, $pass, $entry, $protocol, $server, $port;
51 
55  protected $principal_url;
56 
60  protected $calendar_url;
61 
65  protected $calendar_home_set;
66 
70  protected $calendar_urls;
71 
77  public $user_agent = 'DAViCalClient';
78 
79  protected $headers = array();
80  protected $body = "";
81  protected $requestMethod = "GET";
82  protected $httpRequest = ""; // for debugging http headers sent
83  protected $xmlRequest = ""; // for debugging xml sent
84  protected $xmlResponse = ""; // xml received
85  protected $httpResponseCode = 0; // http response code
86  protected $httpResponseHeaders = "";
87  protected $httpParsedHeaders;
88  protected $httpResponseBody = "";
89 
90  protected $parser; // our XML parser object
91 
92  private $debug = false; // Whether we are debugging
93 
101  function __construct( $base_url, $user, $pass ) {
102  $this->user = $user;
103  $this->pass = $pass;
104  $this->headers = array();
105 
106  if ( preg_match( '#^(https?)://([a-z0-9.-]+)(:([0-9]+))?(/.*)$#', $base_url, $matches ) ) {
107  $this->server = $matches[2];
108  $this->base_url = $matches[5];
109  if ( $matches[1] == 'https' ) {
110  $this->protocol = 'ssl';
111  $this->port = 443;
112  }
113  else {
114  $this->protocol = 'tcp';
115  $this->port = 80;
116  }
117  if ( $matches[4] != '' ) {
118  $this->port = intval($matches[4]);
119  }
120  }
121  else {
122  trigger_error("Invalid URL: '".$base_url."'", E_USER_ERROR);
123  }
124  }
125 
126 
132  function SetDebug( $new_value ) {
133  $old_value = $this->debug;
134  if ( $new_value )
135  $this->debug = true;
136  else
137  $this->debug = false;
138  return $old_value;
139  }
140 
141 
142 
149  function SetMatch( $match, $etag = '*' ) {
150  $this->headers['match'] = sprintf( "%s-Match: \"%s\"", ($match ? "If" : "If-None"), trim($etag,'"'));
151  }
152 
158  function SetDepth( $depth = '0' ) {
159  $this->headers['depth'] = 'Depth: '. ($depth == '1' ? "1" : ($depth == 'infinity' ? $depth : "0") );
160  }
161 
167  function SetUserAgent( $user_agent = null ) {
168  if ( !isset($user_agent) ) $user_agent = $this->user_agent;
169  $this->user_agent = $user_agent;
170  }
171 
177  function SetContentType( $type ) {
178  $this->headers['content-type'] = "Content-type: $type";
179  }
180 
186  function SetCalendar( $url ) {
187  $this->calendar_url = $url;
188  }
189 
195  function ParseResponse( $response ) {
196  $pos = strpos($response, '<?xml');
197  if ($pos !== false) {
198  $this->xmlResponse = trim(substr($response, $pos));
199  $this->xmlResponse = preg_replace('{>[^>]*$}s', '>',$this->xmlResponse );
200  $parser = xml_parser_create_ns('UTF-8');
201  xml_parser_set_option ( $parser, XML_OPTION_SKIP_WHITE, 1 );
202  xml_parser_set_option ( $parser, XML_OPTION_CASE_FOLDING, 0 );
203 
204  if ( xml_parse_into_struct( $parser, $this->xmlResponse, $this->xmlnodes, $this->xmltags ) === 0 ) {
205  printf( "XML parsing error: %s - %s\n", xml_get_error_code($parser), xml_error_string(xml_get_error_code($parser)) );
206 // debug_print_backtrace();
207 // echo "\nNodes array............................................................\n"; print_r( $this->xmlnodes );
208 // echo "\nTags array............................................................\n"; print_r( $this->xmltags );
209  printf( "\nXML Reponse:\n%s\n", $this->xmlResponse );
210  }
211 
212  xml_parser_free($parser);
213  }
214  }
215 
221  function ParseResponseHeaders() {
222  if ( empty($this->httpResponseHeaders) ) return array();
223  if ( !isset($this->httpParsedHeaders) ) {
224  $this->httpParsedHeaders = array();
225  $headers = str_replace("\r\n", "\n", $this->httpResponseHeaders);
226  $ar_headers = explode("\n", $headers);
227  $last_header = '';
228  foreach ($ar_headers as $cur_headers) {
229  if( preg_match( '{^\s*\S}', $cur_headers) ) $header_name = $last_header;
230  else if ( preg_match( '{^(\S*):', $cur_headers, $matches) ) {
231  $header_name = $matches[1];
232  $last_header = $header_name;
233  if ( empty($this->httpParsedHeaders[$header_name]) ) $this->httpParsedHeaders[$header_name] = array();
234  }
235  $this->httpParsedHeaders[$header_name][] = $cur_headers;
236  }
237  }
238  return $this->httpParsedHeaders;
239  }
240 
246  function GetHttpRequest() {
247  return $this->httpRequest;
248  }
254  function GetResponseHeaders() {
255  return $this->httpResponseHeaders;
256  }
262  function GetResponseBody() {
263  return $this->httpResponseBody;
264  }
270  function GetXmlRequest() {
271  return $this->xmlRequest;
272  }
278  function GetXmlResponse() {
279  return $this->xmlResponse;
280  }
281 
289  function DoRequest( $url = null ) {
290  $headers = array();
291 
292  if ( !isset($url) ) $url = $this->base_url;
293  $this->request_url = $url;
294  $url = preg_replace('{^https?://[^/]+}', '', $url);
295  // URLencode if it isn't already
296  if ( preg_match( '{[^%?&=+,.-_/a-z0-9]}', $url ) ) {
297  $url = str_replace(rawurlencode('/'),'/',rawurlencode($url));
298  $url = str_replace(rawurlencode('?'),'?',$url);
299  $url = str_replace(rawurlencode('&'),'&',$url);
300  $url = str_replace(rawurlencode('='),'=',$url);
301  $url = str_replace(rawurlencode('+'),'+',$url);
302  $url = str_replace(rawurlencode(','),',',$url);
303  }
304  $headers[] = $this->requestMethod." ". $url . " HTTP/1.1";
305  $headers[] = "Authorization: Basic ".base64_encode($this->user .":". $this->pass );
306  $headers[] = "Host: ".$this->server .":".$this->port;
307 
308  if ( !isset($this->headers['content-type']) ) $this->headers['content-type'] = "Content-type: text/plain";
309  foreach( $this->headers as $ii => $head ) {
310  $headers[] = $head;
311  }
312  $headers[] = "Content-Length: " . strlen($this->body);
313  $headers[] = "User-Agent: " . $this->user_agent;
314  $headers[] = 'Connection: close';
315  $this->httpRequest = join("\r\n",$headers);
316  $this->xmlRequest = $this->body;
317 
318  $this->xmlResponse = '';
319 
320  $fip = fsockopen( $this->protocol . '://' . $this->server, $this->port, $errno, $errstr, _FSOCK_TIMEOUT); //error handling?
321  if ( !(get_resource_type($fip) == 'stream') ) return false;
322  if ( !fwrite($fip, $this->httpRequest."\r\n\r\n".$this->body) ) { fclose($fip); return false; }
323  $response = "";
324  while( !feof($fip) ) { $response .= fgets($fip,8192); }
325  fclose($fip);
326 
327  list( $this->httpResponseHeaders, $this->httpResponseBody ) = preg_split( '{\r?\n\r?\n}s', $response, 2 );
328  if ( preg_match( '{Transfer-Encoding: chunked}i', $this->httpResponseHeaders ) ) $this->Unchunk();
329  if ( preg_match('/HTTP\/\d\.\d (\d{3})/', $this->httpResponseHeaders, $status) )
330  $this->httpResponseCode = intval($status[1]);
331  else
332  $this->httpResponseCode = 0;
333 
334  $this->headers = array(); // reset the headers array for our next request
335  $this->ParseResponse($this->httpResponseBody);
336  return $response;
337  }
338 
339 
343  function Unchunk() {
344  $content = '';
345  $chunks = $this->httpResponseBody;
346  // printf( "\n================================\n%s\n================================\n", $chunks );
347  do {
348  $bytes = 0;
349  if ( preg_match('{^((\r\n)?\s*([ 0-9a-fA-F]+)(;[^\n]*)?\r?\n)}', $chunks, $matches ) ) {
350  $octets = $matches[3];
351  $bytes = hexdec($octets);
352  $pos = strlen($matches[1]);
353  // printf( "Chunk size 0x%s (%d)\n", $octets, $bytes );
354  if ( $bytes > 0 ) {
355  // printf( "---------------------------------\n%s\n---------------------------------\n", substr($chunks,$pos,$bytes) );
356  $content .= substr($chunks,$pos,$bytes);
357  $chunks = substr($chunks,$pos + $bytes + 2);
358  // printf( "+++++++++++++++++++++++++++++++++\n%s\n+++++++++++++++++++++++++++++++++\n", $chunks );
359  }
360  }
361  else {
362  $content .= $chunks;
363  }
364  }
365  while( $bytes > 0 );
366  $this->httpResponseBody = $content;
367  // printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n%s\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n", $content );
368  }
369 
370 
378  function DoOptionsRequest( $url = null ) {
379  $this->requestMethod = "OPTIONS";
380  $this->body = "";
381  $this->DoRequest($url);
382  $this->ParseResponseHeaders();
383  $allowed = '';
384  foreach( $this->httpParsedHeaders['Allow'] as $allow_header ) {
385  $allowed .= preg_replace( '/^(Allow:)?\s+([a-z, ]+)\r?\n.*/is', '$1,', $allow_header );
386  }
387  $options = array_flip( preg_split( '/[, ]+/', trim($allowed, ', ') ));
388  return $options;
389  }
390 
391 
392 
402  function DoXMLRequest( $request_method, $xml, $url = null ) {
403  $this->body = $xml;
404  $this->requestMethod = $request_method;
405  $this->SetContentType("text/xml");
406  return $this->DoRequest($url);
407  }
408 
409 
410 
416  function DoGETRequest( $url ) {
417  $this->body = "";
418  $this->requestMethod = "GET";
419  return $this->DoRequest( $url );
420  }
421 
422 
428  function DoHEADRequest( $url ) {
429  $this->body = "";
430  $this->requestMethod = "HEAD";
431  return $this->DoRequest( $url );
432  }
433 
434 
444  function DoPUTRequest( $url, $icalendar, $etag = null ) {
445  $this->body = $icalendar;
446 
447  $this->requestMethod = "PUT";
448  if ( $etag != null ) {
449  $this->SetMatch( ($etag != '*'), $etag );
450  }
451  $this->SetContentType('text/calendar; charset="utf-8"');
452  $this->DoRequest($url);
453 
454  $etag = null;
455  if ( preg_match( '{^ETag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders, $matches ) ) $etag = $matches[1];
456  if ( !isset($etag) || $etag == '' ) {
457  if ( $this->debug ) printf( "No etag in:\n%s\n", $this->httpResponseHeaders );
458  $save_request = $this->httpRequest;
459  $save_response_headers = $this->httpResponseHeaders;
460  $this->DoHEADRequest( $url );
461  if ( preg_match( '{^Etag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders, $matches ) ) $etag = $matches[1];
462  if ( !isset($etag) || $etag == '' ) {
463  if ( $this->debug ) printf( "Still No etag in:\n%s\n", $this->httpResponseHeaders );
464  }
465  $this->httpRequest = $save_request;
466  $this->httpResponseHeaders = $save_response_headers;
467  }
468  return $etag;
469  }
470 
471 
480  function DoDELETERequest( $url, $etag = null ) {
481  $this->body = "";
482 
483  $this->requestMethod = "DELETE";
484  if ( $etag != null ) {
485  $this->SetMatch( true, $etag );
486  }
487  $this->DoRequest($url);
488  return $this->httpResponseCode;
489  }
490 
491 
497  function DoPROPFINDRequest( $url, $props, $depth = 0 ) {
498  $this->SetDepth($depth);
499  $xml = new XMLDocument( array( 'DAV:' => '', 'urn:ietf:params:xml:ns:caldav' => 'C' ) );
500  $prop = new XMLElement('prop');
501  foreach( $props AS $v ) {
502  $xml->NSElement($prop,$v);
503  }
504 
505  $this->body = $xml->Render('propfind',$prop );
506 
507  $this->requestMethod = "PROPFIND";
508  $this->SetContentType("text/xml");
509  $this->DoRequest($url);
510  return $this->GetXmlResponse();
511  }
512 
513 
519  function PrincipalURL( $url = null ) {
520  if ( isset($url) ) {
521  $this->principal_url = $url;
522  }
523  return $this->principal_url;
524  }
525 
526 
532  function CalendarHomeSet( $urls = null ) {
533  if ( isset($urls) ) {
534  if ( ! is_array($urls) ) $urls = array($urls);
535  $this->calendar_home_set = $urls;
536  }
537  return $this->calendar_home_set;
538  }
539 
540 
546  function CalendarUrls( $urls = null ) {
547  if ( isset($urls) ) {
548  if ( ! is_array($urls) ) $urls = array($urls);
549  $this->calendar_urls = $urls;
550  }
551  return $this->calendar_urls;
552  }
553 
554 
560  function HrefValueInside( $tagname ) {
561  foreach( $this->xmltags[$tagname] AS $k => $v ) {
562  $j = $v + 1;
563  if ( $this->xmlnodes[$j]['tag'] == 'DAV::href' ) {
564  return rawurldecode($this->xmlnodes[$j]['value']);
565  }
566  }
567  return null;
568  }
569 
570 
577  function HrefForProp( $tagname, $i = 0 ) {
578  if ( isset($this->xmltags[$tagname]) && isset($this->xmltags[$tagname][$i]) ) {
579  $j = $this->xmltags[$tagname][$i];
580  while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::href' ) {
581 // printf( "Node[$j]: %s\n", $this->xmlnodes[$j]['tag']);
582  if ( $this->xmlnodes[$j]['tag'] == 'DAV::status' && $this->xmlnodes[$j]['value'] != 'HTTP/1.1 200 OK' ) return null;
583  }
584 // printf( "Node[$j]: %s\n", $this->xmlnodes[$j]['tag']);
585  if ( $j > 0 && isset($this->xmlnodes[$j]['value']) ) {
586 // printf( "Value[$j]: %s\n", $this->xmlnodes[$j]['value']);
587  return rawurldecode($this->xmlnodes[$j]['value']);
588  }
589  }
590  else {
591  if ( $this->debug ) printf( "xmltags[$tagname] or xmltags[$tagname][$i] is not set\n");
592  }
593  return null;
594  }
595 
596 
603  function HrefForResourcetype( $tagname, $i = 0 ) {
604  if ( isset($this->xmltags[$tagname]) && isset($this->xmltags[$tagname][$i]) ) {
605  $j = $this->xmltags[$tagname][$i];
606  while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::resourcetype' );
607  if ( $j > 0 ) {
608  while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::href' );
609  if ( $j > 0 && isset($this->xmlnodes[$j]['value']) ) {
610  return rawurldecode($this->xmlnodes[$j]['value']);
611  }
612  }
613  }
614  return null;
615  }
616 
617 
623  function GetOKProps( $nodenum ) {
624  $props = null;
625  $level = $this->xmlnodes[$nodenum]['level'];
626  $status = '';
627  while ( $this->xmlnodes[++$nodenum]['level'] >= $level ) {
628  if ( $this->xmlnodes[$nodenum]['tag'] == 'DAV::propstat' ) {
629  if ( $this->xmlnodes[$nodenum]['type'] == 'open' ) {
630  $props = array();
631  $status = '';
632  }
633  else {
634  if ( $status == 'HTTP/1.1 200 OK' ) break;
635  }
636  }
637  elseif ( !isset($this->xmlnodes[$nodenum]) || !is_array($this->xmlnodes[$nodenum]) ) {
638  break;
639  }
640  elseif ( $this->xmlnodes[$nodenum]['tag'] == 'DAV::status' ) {
641  $status = $this->xmlnodes[$nodenum]['value'];
642  }
643  else {
644  $props[] = $this->xmlnodes[$nodenum];
645  }
646  }
647  return $props;
648  }
649 
650 
656  function FindPrincipal( $url=null ) {
657  $xml = $this->DoPROPFINDRequest( $url, array('resourcetype', 'current-user-principal', 'owner', 'principal-URL',
658  'urn:ietf:params:xml:ns:caldav:calendar-home-set'), 1);
659 
660  $principal_url = $this->HrefForProp('DAV::principal');
661 
662  if ( !isset($principal_url) ) {
663  foreach( array('DAV::current-user-principal', 'DAV::principal-URL', 'DAV::owner') AS $href ) {
664  if ( !isset($principal_url) ) {
665  $principal_url = $this->HrefValueInside($href);
666  }
667  }
668  }
669 
670  return $this->PrincipalURL($principal_url);
671  }
672 
673 
679  function FindCalendarHome( $recursed=false ) {
680  if ( !isset($this->principal_url) ) {
681  $this->FindPrincipal();
682  }
683  if ( $recursed ) {
684  $this->DoPROPFINDRequest( $this->principal_url, array('urn:ietf:params:xml:ns:caldav:calendar-home-set'), 0);
685  }
686 
687  $calendar_home = array();
688  foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-home-set'] AS $k => $v ) {
689  if ( $this->xmlnodes[$v]['type'] != 'open' ) continue;
690  while( $this->xmlnodes[++$v]['type'] != 'close' && $this->xmlnodes[$v]['tag'] != 'urn:ietf:params:xml:ns:caldav:calendar-home-set' ) {
691 // printf( "Tag: '%s' = '%s'\n", $this->xmlnodes[$v]['tag'], $this->xmlnodes[$v]['value']);
692  if ( $this->xmlnodes[$v]['tag'] == 'DAV::href' && isset($this->xmlnodes[$v]['value']) )
693  $calendar_home[] = rawurldecode($this->xmlnodes[$v]['value']);
694  }
695  }
696 
697  if ( !$recursed && count($calendar_home) < 1 ) {
698  $calendar_home = $this->FindCalendarHome(true);
699  }
700 
701  return $this->CalendarHomeSet($calendar_home);
702  }
703 
704 
708  function FindCalendars( $recursed=false ) {
709  if ( !isset($this->calendar_home_set[0]) ) {
710  $this->FindCalendarHome();
711  }
712  $this->DoPROPFINDRequest( $this->calendar_home_set[0], array('resourcetype','displayname','http://calendarserver.org/ns/:getctag'), 1);
713 
714  $calendars = array();
715  if ( isset($this->xmltags['urn:ietf:params:xml:ns:caldav:calendar']) ) {
716  $calendar_urls = array();
717  foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar'] AS $k => $v ) {
718  $calendar_urls[$this->HrefForProp('urn:ietf:params:xml:ns:caldav:calendar', $k)] = 1;
719  }
720 
721  foreach( $this->xmltags['DAV::href'] AS $i => $hnode ) {
722  $href = rawurldecode($this->xmlnodes[$hnode]['value']);
723 
724  if ( !isset($calendar_urls[$href]) ) continue;
725 
726 // printf("Seems '%s' is a calendar.\n", $href );
727 
728  $calendar = new CalendarInfo($href);
729  $ok_props = $this->GetOKProps($hnode);
730  foreach( $ok_props AS $v ) {
731 // printf("Looking at: %s[%s]\n", $href, $v['tag'] );
732  switch( $v['tag'] ) {
733  case 'http://calendarserver.org/ns/:getctag':
734  $calendar->getctag = $v['value'];
735  break;
736  case 'DAV::displayname':
737  $calendar->displayname = $v['value'];
738  break;
739  }
740  }
741  $calendars[] = $calendar;
742  }
743  }
744 
745  return $this->CalendarUrls($calendars);
746  }
747 
748 
752  function GetCalendarDetails( $url = null ) {
753  if ( isset($url) ) $this->SetCalendar($url);
754 
755  $calendar_properties = array( 'resourcetype', 'displayname', 'http://calendarserver.org/ns/:getctag', 'urn:ietf:params:xml:ns:caldav:calendar-timezone', 'supported-report-set' );
756  $this->DoPROPFINDRequest( $this->calendar_url, $calendar_properties, 0);
757 
758  $hnode = $this->xmltags['DAV::href'][0];
759  $href = rawurldecode($this->xmlnodes[$hnode]['value']);
760 
761  $calendar = new CalendarInfo($href);
762  $ok_props = $this->GetOKProps($hnode);
763  foreach( $ok_props AS $k => $v ) {
764  $name = preg_replace( '{^.*:}', '', $v['tag'] );
765  if ( isset($v['value'] ) ) {
766  $calendar->{$name} = $v['value'];
767  }
768 /* else {
769  printf( "Calendar property '%s' has no text content\n", $v['tag'] );
770  }*/
771  }
772 
773  return $calendar;
774  }
775 
776 
780  function GetCollectionETags( $url = null ) {
781  if ( isset($url) ) $this->SetCalendar($url);
782 
783  $this->DoPROPFINDRequest( $this->calendar_url, array('getetag'), 1);
784 
785  $etags = array();
786  if ( isset($this->xmltags['DAV::getetag']) ) {
787  foreach( $this->xmltags['DAV::getetag'] AS $k => $v ) {
788  $href = $this->HrefForProp('DAV::getetag', $k);
789  if ( isset($href) && isset($this->xmlnodes[$v]['value']) ) $etags[$href] = $this->xmlnodes[$v]['value'];
790  }
791  }
792 
793  return $etags;
794  }
795 
796 
800  function CalendarMultiget( $event_hrefs, $url = null ) {
801 
802  if ( isset($url) ) $this->SetCalendar($url);
803 
804  $hrefs = '';
805  foreach( $event_hrefs AS $k => $href ) {
806  $href = str_replace( rawurlencode('/'),'/',rawurlencode($href));
807  $hrefs .= '<href>'.$href.'</href>';
808  }
809  $this->body = <<<EOXML
810 <?xml version="1.0" encoding="utf-8" ?>
811 <C:calendar-multiget xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
812 <prop><getetag/><C:calendar-data/></prop>
813 $hrefs
814 </C:calendar-multiget>
815 EOXML;
816 
817  $this->requestMethod = "REPORT";
818  $this->SetContentType("text/xml");
819  $this->DoRequest( $this->calendar_url );
820 
821  $events = array();
822  if ( isset($this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-data']) ) {
823  foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-data'] AS $k => $v ) {
824  $href = $this->HrefForProp('urn:ietf:params:xml:ns:caldav:calendar-data', $k);
825 // echo "Calendar-data:\n"; print_r($this->xmlnodes[$v]);
826  $events[$href] = $this->xmlnodes[$v]['value'];
827  }
828  }
829  else {
830  foreach( $event_hrefs AS $k => $href ) {
831  $this->DoGETRequest($href);
832  $events[$href] = $this->httpResponseBody;
833  }
834  }
835 
836  return $events;
837  }
838 
839 
853  function DoCalendarQuery( $filter, $url = '' ) {
854 
855  if ( !empty($url) ) $this->SetCalendar($url);
856 
857  $this->body = <<<EOXML
858 <?xml version="1.0" encoding="utf-8" ?>
859 <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
860  <D:prop>
861  <C:calendar-data/>
862  <D:getetag/>
863  </D:prop>$filter
864 </C:calendar-query>
865 EOXML;
866 
867  $this->requestMethod = "REPORT";
868  $this->SetContentType("text/xml");
869  $this->DoRequest( $this->calendar_url );
870 
871  $report = array();
872  foreach( $this->xmlnodes as $k => $v ) {
873  switch( $v['tag'] ) {
874  case 'DAV::response':
875  if ( $v['type'] == 'open' ) {
876  $response = array();
877  }
878  elseif ( $v['type'] == 'close' ) {
879  $report[] = $response;
880  }
881  break;
882  case 'DAV::href':
883  $response['href'] = basename( rawurldecode($v['value']) );
884  break;
885  case 'DAV::getetag':
886  $response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
887  break;
888  case 'urn:ietf:params:xml:ns:caldav:calendar-data':
889  $response['data'] = $v['value'];
890  break;
891  }
892  }
893  return $report;
894  }
895 
896 
910  function GetEvents( $start = null, $finish = null, $relative_url = '' ) {
911  $filter = "";
912  if ( isset($start) && isset($finish) )
913  $range = "<C:time-range start=\"$start\" end=\"$finish\"/>";
914  else
915  $range = '';
916 
917  $filter = <<<EOFILTER
918  <C:filter>
919  <C:comp-filter name="VCALENDAR">
920  <C:comp-filter name="VEVENT">
921  $range
922  </C:comp-filter>
923  </C:comp-filter>
924  </C:filter>
925 EOFILTER;
926 
927  return $this->DoCalendarQuery($filter, $relative_url);
928  }
929 
930 
946  function GetTodos( $start, $finish, $completed = false, $cancelled = false, $relative_url = "" ) {
947 
948  if ( $start && $finish ) {
949 $time_range = <<<EOTIME
950  <C:time-range start="$start" end="$finish"/>
951 EOTIME;
952  }
953 
954  // Warning! May contain traces of double negatives...
955  $neg_cancelled = ( $cancelled === true ? "no" : "yes" );
956  $neg_completed = ( $cancelled === true ? "no" : "yes" );
957 
958  $filter = <<<EOFILTER
959  <C:filter>
960  <C:comp-filter name="VCALENDAR">
961  <C:comp-filter name="VTODO">
962  <C:prop-filter name="STATUS">
963  <C:text-match negate-condition="$neg_completed">COMPLETED</C:text-match>
964  </C:prop-filter>
965  <C:prop-filter name="STATUS">
966  <C:text-match negate-condition="$neg_cancelled">CANCELLED</C:text-match>
967  </C:prop-filter>$time_range
968  </C:comp-filter>
969  </C:comp-filter>
970  </C:filter>
971 EOFILTER;
972 
973  return $this->DoCalendarQuery($filter, $relative_url);
974  }
975 
976 
986  function GetEntryByUid( $uid, $relative_url = '', $component_type = 'VEVENT' ) {
987  $filter = "";
988  if ( $uid ) {
989  $filter = <<<EOFILTER
990  <C:filter>
991  <C:comp-filter name="VCALENDAR">
992  <C:comp-filter name="$component_type">
993  <C:prop-filter name="UID">
994  <C:text-match icollation="i;octet">$uid</C:text-match>
995  </C:prop-filter>
996  </C:comp-filter>
997  </C:comp-filter>
998  </C:filter>
999 EOFILTER;
1000  }
1001 
1002  return $this->DoCalendarQuery($filter, $relative_url);
1003  }
1004 
1005 
1013  function GetEntryByHref( $href ) {
1014  $href = str_replace( rawurlencode('/'),'/',rawurlencode($href));
1015  return $this->DoGETRequest( $href );
1016  }
1017 
1018 }
CalendarHomeSet( $urls=null)
DoPUTRequest( $url, $icalendar, $etag=null)
GetOKProps( $nodenum)
GetCollectionETags( $url=null)
SetContentType( $type)
HrefValueInside( $tagname)
HrefForProp( $tagname, $i=0)
DoDELETERequest( $url, $etag=null)
FindPrincipal( $url=null)
DoXMLRequest( $request_method, $xml, $url=null)
DoRequest( $url=null)
GetCalendarDetails( $url=null)
DoCalendarQuery( $filter, $url='')
DoPROPFINDRequest( $url, $props, $depth=0)
ParseResponse( $response)
GetTodos( $start, $finish, $completed=false, $cancelled=false, $relative_url="")
GetEntryByUid( $uid, $relative_url='', $component_type='VEVENT')
GetEvents( $start=null, $finish=null, $relative_url='')
DoOptionsRequest( $url=null)
SetDepth( $depth='0')
SetDebug( $new_value)
CalendarUrls( $urls=null)
SetMatch( $match, $etag=' *')
HrefForResourcetype( $tagname, $i=0)
FindCalendars( $recursed=false)
FindCalendarHome( $recursed=false)
PrincipalURL( $url=null)
SetUserAgent( $user_agent=null)
__construct( $base_url, $user, $pass)
CalendarMultiget( $event_hrefs, $url=null)