source: contrib/davical/inc/caldav-client-v2.php @ 3733

Revision 3733, 31.7 KB checked in by gabriel.malheiros, 13 years ago (diff)

Ticket #1541 - <Davical customizado para o Expresso.Utiliza Caldav e CardDav?>

Line 
1<?php
2/**
3* A Class for connecting to a caldav server
4*
5* @package   awl
6*
7* @subpackage   caldav
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
11*/
12
13require_once('XMLDocument.php');
14
15/**
16* A class for holding basic calendar information
17* @package awl
18*/
19class CalendarInfo {
20  public $url, $displayname, $getctag;
21
22  function __construct( $url, $displayname = null, $getctag = null ) {
23    $this->url = $url;
24    $this->displayname = $displayname;
25    $this->getctag = $getctag;
26  }
27
28  function __toString() {
29    return( '(URL: '.$this->url.'   Ctag: '.$this->getctag.'   Displayname: '.$this->displayname .')'. "\n" );
30  }
31}
32
33
34/**
35* A class for accessing DAViCal via CalDAV, as a client
36*
37* @package   awl
38*/
39class CalDAVClient {
40  /**
41  * Server, username, password, calendar
42  *
43  * @var string
44  */
45  protected $base_url, $user, $pass, $entry, $protocol, $server, $port;
46
47  /**
48  * The principal-URL we're using
49  */
50  protected $principal_url;
51
52  /**
53  * The calendar-URL we're using
54  */
55  protected $calendar_url;
56
57  /**
58  * The calendar-home-set we're using
59  */
60  protected $calendar_home_set;
61
62  /**
63  * The calendar_urls we have discovered
64  */
65  protected $calendar_urls;
66
67  /**
68  * The useragent which is send to the caldav server
69  *
70  * @var string
71  */
72  public $user_agent = 'DAViCalClient';
73
74  protected $headers = array();
75  protected $body = "";
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
81
82  protected $parser; // our XML parser object
83
84  /**
85  * Constructor, initialises the class
86  *
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
90  */
91  function __construct( $base_url, $user, $pass ) {
92    $this->user = $user;
93    $this->pass = $pass;
94    $this->headers = array();
95
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';
101        $this->port = 443;
102      }
103      else {
104        $this->protocol = 'tcp';
105        $this->port = 80;
106      }
107      if ( $matches[4] != '' ) {
108        $this->port = intval($matches[4]);
109      }
110    }
111    else {
112      trigger_error("Invalid URL: '".$base_url."'", E_USER_ERROR);
113    }
114  }
115
116  /**
117  * Adds an If-Match or If-None-Match header
118  *
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.
121  */
122  function SetMatch( $match, $etag = '*' ) {
123    $this->headers['match'] = sprintf( "%s-Match: %s", ($match ? "If" : "If-None"), $etag);
124  }
125
126  /**
127  * Add a Depth: header.  Valid values are 0, 1 or infinity
128  *
129  * @param int $depth  The depth, default to infinity
130  */
131  function SetDepth( $depth = '0' ) {
132    $this->headers['depth'] = 'Depth: '. ($depth == '1' ? "1" : ($depth == 'infinity' ? $depth : "0") );
133  }
134
135  /**
136  * Add a Depth: header.  Valid values are 1 or infinity
137  *
138  * @param int $depth  The depth, default to infinity
139  */
140  function SetUserAgent( $user_agent = null ) {
141    if ( !isset($user_agent) ) $user_agent = $this->user_agent;
142    $this->user_agent = $user_agent;
143  }
144
145  /**
146  * Add a Content-type: header.
147  *
148  * @param string $type  The content type
149  */
150  function SetContentType( $type ) {
151    $this->headers['content-type'] = "Content-type: $type";
152  }
153
154  /**
155  * Set the calendar_url we will be using for a while.
156  *
157  * @param string $url The calendar_url
158  */
159  function SetCalendar( $url ) {
160    $this->calendar_url = $url;
161  }
162
163  /**
164  * Split response into httpResponse and xmlResponse
165  *
166  * @param string Response from server
167   */
168  function ParseResponse( $response ) {
169    $pos = strpos($response, '<?xml');
170    if ($pos === false) {
171      $this->httpResponse = trim($response);
172    }
173    else {
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 );
180
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 );
187      }
188
189      xml_parser_free($parser);
190    }
191  }
192
193  /**
194   * Output http request headers
195   *
196   * @return HTTP headers
197   */
198  function GetHttpRequest() {
199      return $this->httpRequest;
200  }
201  /**
202   * Output http response headers
203   *
204   * @return HTTP headers
205   */
206  function GetResponseHeaders() {
207      return $this->httpResponseHeaders;
208  }
209  /**
210   * Output http response body
211   *
212   * @return HTTP body
213   */
214  function GetResponseBody() {
215      return $this->httpResponseBody;
216  }
217  /**
218   * Output xml request
219   *
220   * @return raw xml
221   */
222  function GetXmlRequest() {
223      return $this->xmlRequest;
224  }
225  /**
226   * Output xml response
227   *
228   * @return raw xml
229   */
230  function GetXmlResponse() {
231      return $this->xmlResponse;
232  }
233
234  /**
235  * Send a request to the server
236  *
237  * @param string $url The URL to make the request to
238  *
239  * @return string The content of the response from the server
240  */
241  function DoRequest( $url = null ) {
242    if(!defined("_FSOCK_TIMEOUT")){ define("_FSOCK_TIMEOUT", 10); }
243    $headers = array();
244
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);
256    }
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;
260
261    if ( !isset($this->headers['content-type']) ) $this->headers['content-type'] = "Content-type: text/plain";
262    foreach( $this->headers as $ii => $head ) {
263      $headers[] = $head;
264    }
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;
270
271    $this->httpResponse = '';
272    $this->xmlResponse = '';
273
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; }
277    $response = "";
278    while( !feof($fip) ) { $response .= fgets($fip,8192); }
279    fclose($fip);
280
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();
283
284    $this->headers = array();  // reset the headers array for our next request
285    $this->ParseResponse($this->httpResponseBody);
286    return $response;
287  }
288
289
290  /**
291  * Unchunk a chunked response
292  */
293  function Unchunk() {
294    $content = '';
295    $chunks = $this->httpResponseBody;
296    // printf( "\n================================\n%s\n================================\n", $chunks );
297    do {
298      $bytes = 0;
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 );
304        if ( $bytes > 0 ) {
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 );
309        }
310      }
311      else {
312        $content .= $chunks;
313      }
314    }
315    while( $bytes > 0 );
316    $this->httpResponseBody = $content;
317    // printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n%s\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n", $content );
318  }
319
320
321  /**
322  * Send an OPTIONS request to the server
323  *
324  * @param string $url The URL to make the request to
325  *
326  * @return array The allowed options
327  */
328  function DoOptionsRequest( $url = null ) {
329    $this->requestMethod = "OPTIONS";
330    $this->body = "";
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 ));
334    return $options;
335  }
336
337
338
339  /**
340  * Send an XML request to the server (e.g. PROPFIND, REPORT, MKCALENDAR)
341  *
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
345  *
346  * @return array An array of the allowed methods
347  */
348  function DoXMLRequest( $request_method, $xml, $url = null ) {
349    $this->body = $xml;
350    $this->requestMethod = $request_method;
351    $this->SetContentType("text/xml");
352    return $this->DoRequest($url);
353  }
354
355
356
357  /**
358  * Get a single item from the server.
359  *
360  * @param string $url The URL to GET
361  */
362  function DoGETRequest( $url ) {
363    $this->body = "";
364    $this->requestMethod = "GET";
365    return $this->DoRequest( $url );
366  }
367
368
369  /**
370  * Get the HEAD of a single item from the server.
371  *
372  * @param string $url The URL to HEAD
373  */
374  function DoHEADRequest( $url ) {
375    $this->body = "";
376    $this->requestMethod = "HEAD";
377    return $this->DoRequest( $url );
378  }
379
380
381  /**
382  * PUT a text/icalendar resource, returning the etag
383  *
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.
387  *
388  * @return string The content of the response from the server
389  */
390  function DoPUTRequest( $url, $icalendar, $etag = null ) {
391    $this->body = $icalendar;
392
393    $this->requestMethod = "PUT";
394    if ( $etag != null ) {
395      $this->SetMatch( ($etag != '*'), $etag );
396    }
397    $this->SetContentType('text/calendar; encoding="utf-8"');
398    $this->DoRequest($url);
399
400    $etag = null;
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 );
410      }
411      $this->httpRequest = $save_request;
412      $this->httpResponseHeaders = $save_response_headers;
413    }
414    return $etag;
415  }
416
417
418  /**
419  * DELETE a text/icalendar resource
420  *
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.
423  *
424  * @return int The HTTP Result Code for the DELETE
425  */
426  function DoDELETERequest( $url, $etag = null ) {
427    $this->body = "";
428
429    $this->requestMethod = "DELETE";
430    if ( $etag != null ) {
431      $this->SetMatch( true, $etag );
432    }
433    $this->DoRequest($url);
434    return $this->resultcode;
435  }
436
437
438  /**
439  * Get a single item from the server.
440  *
441  * @param string $url The URL to PROPFIND on
442  */
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);
449    }
450
451    $this->body = $xml->Render('propfind',$prop );
452
453    $this->requestMethod = "PROPFIND";
454    $this->SetContentType("text/xml");
455    $this->DoRequest($url);
456    return $this->GetXmlResponse();
457  }
458
459
460  /**
461  * Get/Set the Principal URL
462  *
463  * @param $url string The Principal URL to set
464  */
465  function PrincipalURL( $url = null ) {
466    if ( isset($url) ) {
467      $this->principal_url = $url;
468    }
469    return $this->principal_url;
470  }
471
472
473  /**
474  * Get/Set the calendar-home-set URL
475  *
476  * @param $url array of string The calendar-home-set URLs to set
477  */
478  function CalendarHomeSet( $urls = null ) {
479    if ( isset($urls) ) {
480      if ( ! is_array($urls) ) $urls = array($urls);
481      $this->calendar_home_set = $urls;
482    }
483    return $this->calendar_home_set;
484  }
485
486
487  /**
488  * Get/Set the calendar-home-set URL
489  *
490  * @param $urls array of string The calendar URLs to set
491  */
492  function CalendarUrls( $urls = null ) {
493    if ( isset($urls) ) {
494      if ( ! is_array($urls) ) $urls = array($urls);
495      $this->calendar_urls = $urls;
496    }
497    return $this->calendar_urls;
498  }
499
500
501  /**
502  * Return the first occurrence of an href inside the named tag.
503  *
504  * @param string $tagname The tag name to find the href inside of
505  */
506  function HrefValueInside( $tagname ) {
507    foreach( $this->xmltags[$tagname] AS $k => $v ) {
508      $j = $v + 1;
509      if ( $this->xmlnodes[$j]['tag'] == 'DAV::href' ) {
510        return rawurldecode($this->xmlnodes[$j]['value']);
511      }
512    }
513    return null;
514  }
515
516
517  /**
518  * Return the href containing this property.  Except only if it's inside a status != 200
519  *
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
522  */
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;
529      }
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']);
534      }
535    }
536    else {
537      printf( "xmltags[$tagname] or xmltags[$tagname][$i] is not set\n");
538    }
539    return null;
540  }
541
542
543  /**
544  * Return the href which has a resourcetype of the specified type
545  *
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
548  */
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' );
553      if ( $j > 0 ) {
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']);
557        }
558      }
559    }
560    return null;
561  }
562
563
564  /**
565  * Return the <prop> ... </prop> of a propstat where the status is OK
566  *
567  * @param string $nodenum The node number in the xmlnodes which is the href
568  */
569  function GetOKProps( $nodenum ) {
570    $props = null;
571    $level = $this->xmlnodes[$nodenum]['level'];
572    $status = '';
573    while ( $this->xmlnodes[++$nodenum]['level'] >= $level ) {
574      if ( $this->xmlnodes[$nodenum]['tag'] == 'DAV::propstat' ) {
575        if ( $this->xmlnodes[$nodenum]['type'] == 'open' ) {
576          $props = array();
577          $status = '';
578        }
579        else {
580          if ( $status == 'HTTP/1.1 200 OK' ) break;
581        }
582      }
583      elseif ( !isset($this->xmlnodes[$nodenum]) || !is_array($this->xmlnodes[$nodenum]) ) {
584        break;
585      }
586      elseif ( $this->xmlnodes[$nodenum]['tag'] == 'DAV::status' ) {
587        $status = $this->xmlnodes[$nodenum]['value'];
588      }
589      else {
590        $props[] = $this->xmlnodes[$nodenum];
591      }
592    }
593    return $props;
594  }
595
596
597  /**
598  * Attack the given URL in an attempt to find a principal URL
599  *
600  * @param string $url The URL to find the principal-URL from
601  */
602  function FindPrincipal( $url ) {
603    $xml = $this->DoPROPFINDRequest( $url, array('resourcetype', 'current-user-principal', 'owner', 'principal-URL',
604                                  'urn:ietf:params:xml:ns:caldav:calendar-home-set'), 1);
605
606    $principal_url = $this->HrefForProp('DAV::principal');
607
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);
612        }
613      }
614    }
615
616    return $this->PrincipalURL($principal_url);
617  }
618
619
620  /**
621  * Attack the given URL in an attempt to find a principal URL
622  *
623  * @param string $url The URL to find the calendar-home-set from
624  */
625  function FindCalendarHome( $recursed=false ) {
626    if ( !isset($this->principal_url) ) {
627      $this->FindPrincipal();
628    }
629    if ( $recursed ) {
630      $this->DoPROPFINDRequest( $this->principal_url, array('urn:ietf:params:xml:ns:caldav:calendar-home-set'), 0);
631    }
632
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']);
640      }
641    }
642
643    if ( !$recursed && count($calendar_home) < 1 ) {
644      $calendar_home = $this->FindCalendarHome(true);
645    }
646
647    return $this->CalendarHomeSet($calendar_home);
648  }
649
650
651  /**
652  * Find the calendars, from the calendar_home_set
653  */
654  function FindCalendars( $recursed=false ) {
655    if ( !isset($this->calendar_home_set[0]) ) {
656      $this->FindCalendarHome();
657    }
658    $this->DoPROPFINDRequest( $this->calendar_home_set[0], array('resourcetype','displayname','http://calendarserver.org/ns/:getctag'), 1);
659
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;
665      }
666
667      foreach( $this->xmltags['DAV::href'] AS $i => $hnode ) {
668        $href = rawurldecode($this->xmlnodes[$hnode]['value']);
669
670        if ( !isset($calendar_urls[$href]) ) continue;
671
672//        printf("Seems '%s' is a calendar.\n", $href );
673
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'];
681              break;
682            case 'DAV::displayname':
683              $calendar->displayname = $v['value'];
684              break;
685          }
686        }
687        $calendars[] = $calendar;
688      }
689    }
690
691    return $this->CalendarUrls($calendars);
692  }
693
694
695  /**
696  * Find the calendars, from the calendar_home_set
697  */
698  function GetCalendarDetails( $url = null ) {
699    if ( isset($url) ) $this->SetCalendar($url);
700
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);
703
704    $hnode = $this->xmltags['DAV::href'][0];
705    $href = rawurldecode($this->xmlnodes[$hnode]['value']);
706
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'];
713      }
714/*      else {
715        printf( "Calendar property '%s' has no text content\n", $v['tag'] );
716      }*/
717    }
718
719    return $calendar;
720  }
721
722
723  /**
724  * Get all etags for a calendar
725  */
726  function GetCollectionETags( $url = null ) {
727    if ( isset($url) ) $this->SetCalendar($url);
728
729    $this->DoPROPFINDRequest( $this->calendar_url, array('getetag'), 1);
730
731    $etags = array();
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'];
736      }
737    }
738
739    return $etags;
740  }
741
742
743  /**
744  * Get a bunch of events for a calendar with a calendar-multiget report
745  */
746  function CalendarMultiget( $event_hrefs, $url = null ) {
747
748    if ( isset($url) ) $this->SetCalendar($url);
749
750    $hrefs = '';
751    foreach( $event_hrefs AS $k => $href ) {
752      $href = str_replace( rawurlencode('/'),'/',rawurlencode($href));
753      $hrefs .= '<href>'.$href.'</href>';
754    }
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>
759$hrefs
760</C:calendar-multiget>
761EOXML;
762
763    $this->requestMethod = "REPORT";
764    $this->SetContentType("text/xml");
765    $this->DoRequest( $this->calendar_url );
766
767    $events = array();
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'];
773      }
774    }
775    else {
776      foreach( $event_hrefs AS $k => $href ) {
777        $this->DoGETRequest($href);
778        $events[$href] = $this->httpResponseBody;
779      }
780    }
781
782    return $events;
783  }
784
785
786  /**
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.
791  *
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 null to use the 'current' calendar_url
794  *
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.
798  */
799  function DoCalendarQuery( $filter, $url = null ) {
800
801    if ( isset($url) ) $this->SetCalendar($url);
802
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">
806  <D:prop>
807    <C:calendar-data/>
808    <D:getetag/>
809  </D:prop>$filter
810</C:calendar-query>
811EOXML;
812
813    $this->requestMethod = "REPORT";
814    $this->SetContentType("text/xml");
815    $this->DoRequest( $this->calendar_url );
816
817    $report = array();
818    foreach( $this->xmltags as $k => $v ) {
819      switch( $v['tag'] ) {
820        case 'DAV::response':
821          if ( $v['type'] == 'open' ) {
822            $response = array();
823          }
824          elseif ( $v['type'] == 'close' ) {
825            $report[] = $response;
826          }
827          break;
828        case 'DAV::href':
829          $response['href'] = basename( rawurldecode($v['value']) );
830          break;
831        case 'DAV::getetag':
832          $response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
833          break;
834        case 'urn:ietf:params:xml:ns:caldav:calendar-data':
835          $response['data'] = $v['value'];
836          break;
837      }
838    }
839    return $report;
840  }
841
842
843  /**
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.
849  *
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 ''.
853  *
854  * @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
855  */
856  function GetEvents( $start = null, $finish = null, $relative_url = '' ) {
857    $filter = "";
858    if ( isset($start) && isset($finish) )
859        $range = "<C:time-range start=\"$start\" end=\"$finish\"/>";
860    else
861        $range = '';
862
863    $filter = <<<EOFILTER
864  <C:filter>
865    <C:comp-filter name="VCALENDAR">
866      <C:comp-filter name="VEVENT">
867        $range
868      </C:comp-filter>
869    </C:comp-filter>
870  </C:filter>
871EOFILTER;
872
873    return $this->DoCalendarQuery($filter, $relative_url);
874  }
875
876
877  /**
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.
883  *
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 ''.
889  *
890  * @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
891  */
892  function GetTodos( $start, $finish, $completed = false, $cancelled = false, $relative_url = "" ) {
893
894    if ( $start && $finish ) {
895$time_range = <<<EOTIME
896                <C:time-range start="$start" end="$finish"/>
897EOTIME;
898    }
899
900    // Warning!  May contain traces of double negatives...
901    $neg_cancelled = ( $cancelled === true ? "no" : "yes" );
902    $neg_completed = ( $cancelled === true ? "no" : "yes" );
903
904    $filter = <<<EOFILTER
905  <C:filter>
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>
910                </C:prop-filter>
911                <C:prop-filter name="STATUS">
912                        <C:text-match negate-condition="$neg_cancelled">CANCELLED</C:text-match>
913                </C:prop-filter>$time_range
914          </C:comp-filter>
915    </C:comp-filter>
916  </C:filter>
917EOFILTER;
918
919    return $this->DoCalendarQuery($filter, $relative_url);
920  }
921
922
923  /**
924  * Get the calendar entry by UID
925  *
926  * @param uid
927  * @param string    $relative_url The URL relative to the base_url specified when the calendar was opened.  Default ''.
928  *
929  * @return array An array of the relative URL, etag, and calendar data returned from DoCalendarQuery() @see DoCalendarQuery()
930  */
931  function GetEntryByUid( $uid, $relative_url = '' ) {
932    $filter = "";
933    if ( $uid ) {
934      $filter = <<<EOFILTER
935  <C:filter>
936    <C:comp-filter name="VCALENDAR">
937          <C:comp-filter name="VEVENT">
938                <C:prop-filter name="UID">
939                        <C:text-match icollation="i;octet">$uid</C:text-match>
940                </C:prop-filter>
941          </C:comp-filter>
942    </C:comp-filter>
943  </C:filter>
944EOFILTER;
945    }
946
947    return $this->DoCalendarQuery($filter, $relative_url);
948  }
949
950
951  /**
952  * Get the calendar entry by HREF
953  *
954  * @param string    $href         The href from a call to GetEvents or GetTodos etc.
955  *
956  * @return string The iCalendar of the calendar entry
957  */
958  function GetEntryByHref( $href ) {
959    $href = str_replace( rawurlencode('/'),'/',rawurlencode($href));
960    return $this->DoGETRequest( $href );
961  }
962
963}
964
965/**
966* Usage example
967*
968* $cal = new CalDAVClient( "http://calendar.example.com/caldav.php/username/calendar/", "username", "password", "calendar" );
969* $options = $cal->DoOptionsRequest();
970* if ( isset($options["PROPFIND"]) ) {
971*   // Fetch some information about the events in that calendar
972*   $cal->SetDepth(1);
973*   $folder_xml = $cal->DoXMLRequest("PROPFIND", '<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><getcontentlength/><getcontenttype/><resourcetype/><getetag/></prop></propfind>' );
974* }
975* // Fetch all events for February
976* $events = $cal->GetEvents("20070101T000000Z","20070201T000000Z");
977* foreach ( $events AS $k => $event ) {
978*   do_something_with_event_data( $event['data'] );
979* }
980* $acc = array();
981* $acc["google"] = array(
982* "user"=>"kunsttherapie@gmail.com",
983* "pass"=>"xxxxx",
984* "server"=>"ssl://www.google.com",
985* "port"=>"443",
986* "uri"=>"https://www.google.com/calendar/dav/kunsttherapie@gmail.com/events/",
987* );
988*
989* $acc["davical"] = array(
990* "user"=>"some_user",
991* "pass"=>"big secret",
992* "server"=>"calendar.foo.bar",
993* "port"=>"80",
994* "uri"=>"http://calendar.foo.bar/caldav.php/some_user/home/",
995* );
996* //*******************************
997*
998* $account = $acc["davical"];
999*
1000* //*******************************
1001* $cal = new CalDAVClient( $account["uri"], $account["user"], $account["pass"], "", $account["server"], $account["port"] );
1002* $options = $cal->DoOptionsRequest();
1003* print_r($options);
1004*
1005* //*******************************
1006* //*******************************
1007*
1008* $xmlC = <<<PROPP
1009* <?xml version="1.0" encoding="utf-8" ?>
1010* <D:propfind xmlns:D="DAV:" xmlns:C="http://calendarserver.org/ns/">
1011*     <D:prop>
1012*             <D:displayname />
1013*             <C:getctag />
1014*             <D:resourcetype />
1015*
1016*     </D:prop>
1017* </D:propfind>
1018* PROPP;
1019* //if ( isset($options["PROPFIND"]) ) {
1020*   // Fetch some information about the events in that calendar
1021* //  $cal->SetDepth(1);
1022* //  $folder_xml = $cal->DoXMLRequest("PROPFIND", $xmlC);
1023* //  print_r( $folder_xml);
1024* //}
1025*
1026* // Fetch all events for February
1027* $events = $cal->GetEvents("20090201T000000Z","20090301T000000Z");
1028* foreach ( $events as $k => $event ) {
1029*     print_r($event['data']);
1030*     print "\n---------------------------------------------\n";
1031* }
1032*
1033* //*******************************
1034* //*******************************
1035*/
Note: See TracBrowser for help on using the repository browser.