source: trunk/prototype/plugins/davicalCliente/caldav-client-v2.php @ 5399

Revision 5399, 32.4 KB checked in by cristiano, 12 years ago (diff)

Ticket #2434 - Alteração da estrutura de diretórios da nova API

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 ROOTPATH.'/plugins/davicalCliente/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 ExpressoLivre';
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  * Send an MOVE request to the server
357  * Exp: (Move collection) $cal->DoMOVERequest( colloection1/ , collection2/ );
358  * @param string $method origem
359  * @param string $destination destination
360  *
361  * @return string The content of the response from the server
362  */
363  function DoMOVERequest( $origem, $destination) {
364    $this->requestMethod = 'MOVE';
365    $this->headers[] = 'Destination: '.$this->base_url.$destination;
366    return $this->DoRequest($this->base_url.urlencode($origem));
367  }
368
369
370  /**
371  * Get a single item from the server.
372  *
373  * @param string $url The URL to GET
374  */
375  function DoGETRequest( $url ) {
376    $this->body = "";
377    $this->requestMethod = "GET";
378    return $this->DoRequest( $url );
379  }
380
381
382  /**
383  * Get the HEAD of a single item from the server.
384  *
385  * @param string $url The URL to HEAD
386  */
387  function DoHEADRequest( $url ) {
388    $this->body = "";
389    $this->requestMethod = "HEAD";
390    return $this->DoRequest( $url );
391  }
392
393
394  /**
395  * PUT a text/icalendar resource, returning the etag
396  *
397  * @param string $url The URL to make the request to
398  * @param string $icalendar The iCalendar resource to send to the server
399  * @param string $etag The etag of an existing resource to be overwritten, or '*' for a new resource.
400  *
401  * @return string The content of the response from the server
402  */
403  function DoPUTRequest( $url, $icalendar, $etag = null ) {
404    $this->body = $icalendar;
405
406    $this->requestMethod = "PUT";
407    if ( $etag != null ) {
408      $this->SetMatch( ($etag != '*'), $etag );
409    }
410    $this->SetContentType('text/calendar; encoding="utf-8"');
411    $this->DoRequest($url);
412   
413    var_dump( $this->httpResponseHeaders);
414   
415    $etag = null;
416    if ( preg_match( '{^ETag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders, $matches ) ) $etag = $matches[1];
417    if ( !isset($etag) || $etag == '' ) {
418      printf( "No etag in:\n%s\n", $this->httpResponseHeaders );
419      $save_request = $this->httpRequest;
420      $save_response_headers = $this->httpResponseHeaders;
421      $this->DoHEADRequest( $url );
422      if ( preg_match( '{^Etag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders, $matches ) ) $etag = $matches[1];
423      if ( !isset($etag) || $etag == '' ) {
424        printf( "Still No etag in:\n%s\n", $this->httpResponseHeaders );
425      }
426      $this->httpRequest = $save_request;
427      $this->httpResponseHeaders = $save_response_headers;
428    }
429    return $etag;
430  }
431
432
433  /**
434  * DELETE a text/icalendar resource
435  *
436  * @param string $url The URL to make the request to
437  * @param string $etag The etag of an existing resource to be deleted, or '*' for any resource at that URL.
438  *
439  * @return int The HTTP Result Code for the DELETE
440  */
441  function DoDELETERequest( $url, $etag = null ) {
442    $this->body = "";
443
444    $this->requestMethod = "DELETE";
445    if ( $etag != null ) {
446      $this->SetMatch( true, $etag );
447    }
448    $this->DoRequest($url);
449    return $this->resultcode;
450  }
451
452
453  /**
454  * Get a single item from the server.
455  *
456  * @param string $url The URL to PROPFIND on
457  */
458  function DoPROPFINDRequest( $url, $props, $depth = 0 ) {
459    $this->SetDepth($depth);
460    $xml = new XMLDocument( array( 'DAV:' => '', 'urn:ietf:params:xml:ns:caldav' => 'C' ) );
461    $prop = new XMLElement('prop');
462    foreach( $props AS $v ) {
463      $xml->NSElement($prop,$v);
464    }
465
466    $this->body = $xml->Render('propfind',$prop );
467
468    $this->requestMethod = "PROPFIND";
469    $this->SetContentType("text/xml");
470    $this->DoRequest($url);
471    return $this->GetXmlResponse();
472  }
473
474
475  /**
476  * Get/Set the Principal URL
477  *
478  * @param $url string The Principal URL to set
479  */
480  function PrincipalURL( $url = null ) {
481    if ( isset($url) ) {
482      $this->principal_url = $url;
483    }
484    return $this->principal_url;
485  }
486
487
488  /**
489  * Get/Set the calendar-home-set URL
490  *
491  * @param $url array of string The calendar-home-set URLs to set
492  */
493  function CalendarHomeSet( $urls = null ) {
494    if ( isset($urls) ) {
495      if ( ! is_array($urls) ) $urls = array($urls);
496      $this->calendar_home_set = $urls;
497    }
498    return $this->calendar_home_set;
499  }
500
501
502  /**
503  * Get/Set the calendar-home-set URL
504  *
505  * @param $urls array of string The calendar URLs to set
506  */
507  function CalendarUrls( $urls = null ) {
508    if ( isset($urls) ) {
509      if ( ! is_array($urls) ) $urls = array($urls);
510      $this->calendar_urls = $urls;
511    }
512    return $this->calendar_urls;
513  }
514
515
516  /**
517  * Return the first occurrence of an href inside the named tag.
518  *
519  * @param string $tagname The tag name to find the href inside of
520  */
521  function HrefValueInside( $tagname ) {
522    foreach( $this->xmltags[$tagname] AS $k => $v ) {
523      $j = $v + 1;
524      if ( $this->xmlnodes[$j]['tag'] == 'DAV::href' ) {
525        return rawurldecode($this->xmlnodes[$j]['value']);
526      }
527    }
528    return null;
529  }
530
531
532  /**
533  * Return the href containing this property.  Except only if it's inside a status != 200
534  *
535  * @param string $tagname The tag name of the property to find the href for
536  * @param integer $which Which instance of the tag should we use
537  */
538  function HrefForProp( $tagname, $i = 0 ) {
539    if ( isset($this->xmltags[$tagname]) && isset($this->xmltags[$tagname][$i]) ) {
540      $j = $this->xmltags[$tagname][$i];
541      while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::href' ) {
542//        printf( "Node[$j]: %s\n", $this->xmlnodes[$j]['tag']);
543        if ( $this->xmlnodes[$j]['tag'] == 'DAV::status' && $this->xmlnodes[$j]['value'] != 'HTTP/1.1 200 OK' ) return null;
544      }
545//      printf( "Node[$j]: %s\n", $this->xmlnodes[$j]['tag']);
546      if ( $j > 0 && isset($this->xmlnodes[$j]['value']) ) {
547//        printf( "Value[$j]: %s\n", $this->xmlnodes[$j]['value']);
548        return rawurldecode($this->xmlnodes[$j]['value']);
549      }
550    }
551    else {
552      printf( "xmltags[$tagname] or xmltags[$tagname][$i] is not set\n");
553    }
554    return null;
555  }
556
557
558  /**
559  * Return the href which has a resourcetype of the specified type
560  *
561  * @param string $tagname The tag name of the resourcetype to find the href for
562  * @param integer $which Which instance of the tag should we use
563  */
564  function HrefForResourcetype( $tagname, $i = 0 ) {
565    if ( isset($this->xmltags[$tagname]) && isset($this->xmltags[$tagname][$i]) ) {
566      $j = $this->xmltags[$tagname][$i];
567      while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::resourcetype' );
568      if ( $j > 0 ) {
569        while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::href' );
570        if ( $j > 0 && isset($this->xmlnodes[$j]['value']) ) {
571          return rawurldecode($this->xmlnodes[$j]['value']);
572        }
573      }
574    }
575    return null;
576  }
577
578
579  /**
580  * Return the <prop> ... </prop> of a propstat where the status is OK
581  *
582  * @param string $nodenum The node number in the xmlnodes which is the href
583  */
584  function GetOKProps( $nodenum ) {
585    $props = null;
586    $level = $this->xmlnodes[$nodenum]['level'];
587    $status = '';
588    while ( $this->xmlnodes[++$nodenum]['level'] >= $level ) {
589      if ( $this->xmlnodes[$nodenum]['tag'] == 'DAV::propstat' ) {
590        if ( $this->xmlnodes[$nodenum]['type'] == 'open' ) {
591          $props = array();
592          $status = '';
593        }
594        else {
595          if ( $status == 'HTTP/1.1 200 OK' ) break;
596        }
597      }
598      elseif ( !isset($this->xmlnodes[$nodenum]) || !is_array($this->xmlnodes[$nodenum]) ) {
599        break;
600      }
601      elseif ( $this->xmlnodes[$nodenum]['tag'] == 'DAV::status' ) {
602        $status = $this->xmlnodes[$nodenum]['value'];
603      }
604      else {
605        $props[] = $this->xmlnodes[$nodenum];
606      }
607    }
608    return $props;
609  }
610
611
612  /**
613  * Attack the given URL in an attempt to find a principal URL
614  *
615  * @param string $url The URL to find the principal-URL from
616  */
617  function FindPrincipal( $url ) {
618    $xml = $this->DoPROPFINDRequest( $url, array('resourcetype', 'current-user-principal', 'owner', 'principal-URL',
619                                  'urn:ietf:params:xml:ns:caldav:calendar-home-set'), 1);
620
621    $principal_url = $this->HrefForProp('DAV::principal');
622
623    if ( !isset($principal_url) ) {
624      foreach( array('DAV::current-user-principal', 'DAV::principal-URL', 'DAV::owner') AS $href ) {
625        if ( !isset($principal_url) ) {
626          $principal_url = $this->HrefValueInside($href);
627        }
628      }
629    }
630
631    return $this->PrincipalURL($principal_url);
632  }
633
634
635  /**
636  * Attack the given URL in an attempt to find a principal URL
637  *
638  * @param string $url The URL to find the calendar-home-set from
639  */
640  function FindCalendarHome( $recursed=false ) {
641    if ( !isset($this->principal_url) ) {
642      $this->FindPrincipal($this->principal_url);
643    }
644    if ( $recursed ) {
645      $this->DoPROPFINDRequest( $this->principal_url, array('urn:ietf:params:xml:ns:caldav:calendar-home-set'), 0);
646    }
647
648    $calendar_home = array();
649    foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-home-set'] AS $k => $v ) {
650      if ( $this->xmlnodes[$v]['type'] != 'open' ) continue;
651      while( $this->xmlnodes[++$v]['type'] != 'close' && $this->xmlnodes[$v]['tag'] != 'urn:ietf:params:xml:ns:caldav:calendar-home-set' ) {
652//        printf( "Tag: '%s' = '%s'\n", $this->xmlnodes[$v]['tag'], $this->xmlnodes[$v]['value']);
653        if ( $this->xmlnodes[$v]['tag'] == 'DAV::href' && isset($this->xmlnodes[$v]['value']) )
654          $calendar_home[] = rawurldecode($this->xmlnodes[$v]['value']);
655      }
656    }
657
658    if ( !$recursed && count($calendar_home) < 1 ) {
659      $calendar_home = $this->FindCalendarHome(true);
660    }
661
662    return $this->CalendarHomeSet($calendar_home);
663  }
664
665
666  /**
667  * Find the calendars, from the calendar_home_set
668  */
669  function FindCalendars( $recursed=false ) {
670    if ( !isset($this->calendar_home_set[0]) ) {
671      $this->FindCalendarHome();
672    }
673    $this->DoPROPFINDRequest( $this->calendar_home_set[0], array('resourcetype','displayname','http://calendarserver.org/ns/:getctag'), 1);
674
675    $calendars = array();
676    if ( isset($this->xmltags['urn:ietf:params:xml:ns:caldav:calendar']) ) {
677      $calendar_urls = array();
678      foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar'] AS $k => $v ) {
679        $calendar_urls[$this->HrefForProp('urn:ietf:params:xml:ns:caldav:calendar', $k)] = 1;
680      }
681
682      foreach( $this->xmltags['DAV::href'] AS $i => $hnode ) {
683        $href = rawurldecode($this->xmlnodes[$hnode]['value']);
684
685        if ( !isset($calendar_urls[$href]) ) continue;
686
687//        printf("Seems '%s' is a calendar.\n", $href );
688
689        $calendar = new CalendarInfo($href);
690        $ok_props = $this->GetOKProps($hnode);
691        foreach( $ok_props AS $v ) {
692//          printf("Looking at: %s[%s]\n", $href, $v['tag'] );
693          switch( $v['tag'] ) {
694            case 'http://calendarserver.org/ns/:getctag':
695              $calendar->getctag = $v['value'];
696              break;
697            case 'DAV::displayname':
698              $calendar->displayname = isset($v['value']) ? $v['value'] : '';
699              break;
700          }
701        }
702        $calendars[] = $calendar;
703      }
704    }
705
706    return $this->CalendarUrls($calendars);
707  }
708
709
710  /**
711  * Find the calendars, from the calendar_home_set
712  */
713  function GetCalendarDetails( $url = null ) {
714    if ( isset($url) ) $this->SetCalendar($url);
715
716    $calendar_properties = array( 'resourcetype', 'displayname', 'http://calendarserver.org/ns/:getctag', 'urn:ietf:params:xml:ns:caldav:calendar-timezone', 'supported-report-set' );
717    $this->DoPROPFINDRequest( $this->calendar_url, $calendar_properties, 0);
718
719    $hnode = $this->xmltags['DAV::href'][0];
720    $href = rawurldecode($this->xmlnodes[$hnode]['value']);
721
722    $calendar = new CalendarInfo($href);
723    $ok_props = $this->GetOKProps($hnode);
724    foreach( $ok_props AS $k => $v ) {
725      $name = preg_replace( '{^.*:}', '', $v['tag'] );
726      if ( isset($v['value'] ) ) {
727        $calendar->{$name} = $v['value'];
728      }
729/*      else {
730        printf( "Calendar property '%s' has no text content\n", $v['tag'] );
731      }*/
732    }
733
734    return $calendar;
735  }
736
737
738  /**
739  * Get all etags for a calendar
740  */
741  function GetCollectionETags( $url = null ) {
742    if ( isset($url) ) $this->SetCalendar($url);
743
744    $this->DoPROPFINDRequest( $this->calendar_url, array('getetag'), 1);
745
746    $etags = array();
747    if ( isset($this->xmltags['DAV::getetag']) ) {
748      foreach( $this->xmltags['DAV::getetag'] AS $k => $v ) {
749        $href = $this->HrefForProp('DAV::getetag', $k);
750        if ( isset($href) && isset($this->xmlnodes[$v]['value']) ) $etags[$href] = $this->xmlnodes[$v]['value'];
751      }
752    }
753
754    return $etags;
755  }
756
757
758  /**
759  * Get a bunch of events for a calendar with a calendar-multiget report
760  */
761  function CalendarMultiget( $event_hrefs, $url = null ) {
762
763    if ( isset($url) ) $this->SetCalendar($url);
764
765    $hrefs = '';
766    foreach( $event_hrefs AS $k => $href ) {
767      $href = str_replace( rawurlencode('/'),'/',rawurlencode($href));
768      $hrefs .= '<href>'.$href.'</href>';
769    }
770    $this->body = <<<EOXML
771<?xml version="1.0" encoding="utf-8" ?>
772<C:calendar-multiget xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
773<prop><getetag/><C:calendar-data/></prop>
774$hrefs
775</C:calendar-multiget>
776EOXML;
777
778    $this->requestMethod = "REPORT";
779    $this->SetContentType("text/xml");
780    $this->DoRequest( $this->calendar_url );
781
782    $events = array();
783    if ( isset($this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-data']) ) {
784      foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-data'] AS $k => $v ) {
785        $href = $this->HrefForProp('urn:ietf:params:xml:ns:caldav:calendar-data', $k);
786//        echo "Calendar-data:\n"; print_r($this->xmlnodes[$v]);
787        $events[$href] = $this->xmlnodes[$v]['value'];
788      }
789    }
790    else {
791      foreach( $event_hrefs AS $k => $href ) {
792        $this->DoGETRequest($href);
793        $events[$href] = $this->httpResponseBody;
794      }
795    }
796
797    return $events;
798  }
799
800
801  /**
802  * Given XML for a calendar query, return an array of the events (/todos) in the
803  * response.  Each event in the array will have a 'href', 'etag' and '$response_type'
804  * part, where the 'href' is relative to the calendar and the '$response_type' contains the
805  * definition of the calendar data in iCalendar format.
806  *
807  * @param string $filter XML fragment which is the <filter> element of a calendar-query
808  * @param string $url The URL of the calendar, or null to use the 'current' calendar_url
809  *
810  * @return array An array of the relative URLs, etags, and events from the server.  Each element of the array will
811  *               be an array with 'href', 'etag' and 'data' elements, corresponding to the URL, the server-supplied
812  *               etag (which only varies when the data changes) and the calendar data in iCalendar format.
813  */
814  function DoCalendarQuery( $filter, $url = null ) {
815
816    if ( isset($url) ) $this->SetCalendar($url);
817
818    $this->body = <<<EOXML
819<?xml version="1.0" encoding="utf-8" ?>
820<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
821  <D:prop>
822    <C:calendar-data/>
823    <D:getetag/>
824  </D:prop>$filter
825</C:calendar-query>
826EOXML;
827
828    $this->requestMethod = "REPORT";
829    $this->SetContentType("text/xml");
830    $this->DoRequest( $this->calendar_url );
831
832    $report = array();
833    foreach( $this->xmltags as $k => $v ) {
834      switch( $v['tag'] ) {
835        case 'DAV::response':
836          if ( $v['type'] == 'open' ) {
837            $response = array();
838          }
839          elseif ( $v['type'] == 'close' ) {
840            $report[] = $response;
841          }
842          break;
843        case 'DAV::href':
844          $response['href'] = basename( rawurldecode($v['value']) );
845          break;
846        case 'DAV::getetag':
847          $response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
848          break;
849        case 'urn:ietf:params:xml:ns:caldav:calendar-data':
850          $response['data'] = $v['value'];
851          break;
852      }
853    }
854    return $report;
855  }
856
857
858  /**
859  * Get the events in a range from $start to $finish.  The dates should be in the
860  * format yyyymmddThhmmssZ and should be in GMT.  The events are returned as an
861  * array of event arrays.  Each event array will have a 'href', 'etag' and 'event'
862  * part, where the 'href' is relative to the calendar and the event contains the
863  * definition of the event in iCalendar format.
864  *
865  * @param timestamp $start The start time for the period
866  * @param timestamp $finish The finish time for the period
867  * @param string    $relative_url The URL relative to the base_url specified when the calendar was opened.  Default ''.
868  *
869  * @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
870  */
871  function GetEvents( $start = null, $finish = null, $relative_url = '' ) {
872    $filter = "";
873    if ( isset($start) && isset($finish) )
874        $range = "<C:time-range start=\"$start\" end=\"$finish\"/>";
875    else
876        $range = '';
877
878    $filter = <<<EOFILTER
879  <C:filter>
880    <C:comp-filter name="VCALENDAR">
881      <C:comp-filter name="VEVENT">
882        $range
883      </C:comp-filter>
884    </C:comp-filter>
885  </C:filter>
886EOFILTER;
887
888    return $this->DoCalendarQuery($filter, $relative_url);
889  }
890
891
892  /**
893  * Get the todo's in a range from $start to $finish.  The dates should be in the
894  * format yyyymmddThhmmssZ and should be in GMT.  The events are returned as an
895  * array of event arrays.  Each event array will have a 'href', 'etag' and 'event'
896  * part, where the 'href' is relative to the calendar and the event contains the
897  * definition of the event in iCalendar format.
898  *
899  * @param timestamp $start The start time for the period
900  * @param timestamp $finish The finish time for the period
901  * @param boolean   $completed Whether to include completed tasks
902  * @param boolean   $cancelled Whether to include cancelled tasks
903  * @param string    $relative_url The URL relative to the base_url specified when the calendar was opened.  Default ''.
904  *
905  * @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
906  */
907  function GetTodos( $start, $finish, $completed = false, $cancelled = false, $relative_url = "" ) {
908
909    if ( $start && $finish ) {
910$time_range = <<<EOTIME
911                <C:time-range start="$start" end="$finish"/>
912EOTIME;
913    }
914
915    // Warning!  May contain traces of double negatives...
916    $neg_cancelled = ( $cancelled === true ? "no" : "yes" );
917    $neg_completed = ( $cancelled === true ? "no" : "yes" );
918
919    $filter = <<<EOFILTER
920  <C:filter>
921    <C:comp-filter name="VCALENDAR">
922          <C:comp-filter name="VTODO">
923                <C:prop-filter name="STATUS">
924                        <C:text-match negate-condition="$neg_completed">COMPLETED</C:text-match>
925                </C:prop-filter>
926                <C:prop-filter name="STATUS">
927                        <C:text-match negate-condition="$neg_cancelled">CANCELLED</C:text-match>
928                </C:prop-filter>$time_range
929          </C:comp-filter>
930    </C:comp-filter>
931  </C:filter>
932EOFILTER;
933
934    return $this->DoCalendarQuery($filter, $relative_url);
935  }
936
937
938  /**
939  * Get the calendar entry by UID
940  *
941  * @param uid
942  * @param string    $relative_url The URL relative to the base_url specified when the calendar was opened.  Default ''.
943  *
944  * @return array An array of the relative URL, etag, and calendar data returned from DoCalendarQuery() @see DoCalendarQuery()
945  */
946  function GetEntryByUid( $uid, $relative_url = '' ) {
947    $filter = "";
948    if ( $uid ) {
949      $filter = <<<EOFILTER
950  <C:filter>
951    <C:comp-filter name="VCALENDAR">
952          <C:comp-filter name="VEVENT">
953                <C:prop-filter name="UID">
954                        <C:text-match icollation="i;octet">$uid</C:text-match>
955                </C:prop-filter>
956          </C:comp-filter>
957    </C:comp-filter>
958  </C:filter>
959EOFILTER;
960    }
961
962    return $this->DoCalendarQuery($filter, $relative_url);
963  }
964
965
966  /**
967  * Get the calendar entry by HREF
968  *
969  * @param string    $href         The href from a call to GetEvents or GetTodos etc.
970  *
971  * @return string The iCalendar of the calendar entry
972  */
973  function GetEntryByHref( $href ) {
974    $href = str_replace( rawurlencode('/'),'/',rawurlencode($href));
975    return $this->DoGETRequest( $href );
976  }
977
978}
979
980/**
981* Usage example
982*
983* $cal = new CalDAVClient( "http://calendar.example.com/caldav.php/username/calendar/", "username", "password", "calendar" );
984* $options = $cal->DoOptionsRequest();
985* if ( isset($options["PROPFIND"]) ) {
986*   // Fetch some information about the events in that calendar
987*   $cal->SetDepth(1);
988*   $folder_xml = $cal->DoXMLRequest("PROPFIND", '<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><getcontentlength/><getcontenttype/><resourcetype/><getetag/></prop></propfind>' );
989* }
990* // Fetch all events for February
991* $events = $cal->GetEvents("20070101T000000Z","20070201T000000Z");
992* foreach ( $events AS $k => $event ) {
993*   do_something_with_event_data( $event['data'] );
994* }
995* $acc = array();
996* $acc["google"] = array(
997* "user"=>"kunsttherapie@gmail.com",
998* "pass"=>"xxxxx",
999* "server"=>"ssl://www.google.com",
1000* "port"=>"443",
1001* "uri"=>"https://www.google.com/calendar/dav/kunsttherapie@gmail.com/events/",
1002* );
1003*
1004* $acc["davical"] = array(
1005* "user"=>"some_user",
1006* "pass"=>"big secret",
1007* "server"=>"calendar.foo.bar",
1008* "port"=>"80",
1009* "uri"=>"http://calendar.foo.bar/caldav.php/some_user/home/",
1010* );
1011* //*******************************
1012*
1013* $account = $acc["davical"];
1014*
1015* //*******************************
1016* $cal = new CalDAVClient( $account["uri"], $account["user"], $account["pass"], "", $account["server"], $account["port"] );
1017* $options = $cal->DoOptionsRequest();
1018* print_r($options);
1019*
1020* //*******************************
1021* //*******************************
1022*
1023* $xmlC = <<<PROPP
1024* <?xml version="1.0" encoding="utf-8" ?>
1025* <D:propfind xmlns:D="DAV:" xmlns:C="http://calendarserver.org/ns/">
1026*     <D:prop>
1027*             <D:displayname />
1028*             <C:getctag />
1029*             <D:resourcetype />
1030*
1031*     </D:prop>
1032* </D:propfind>
1033* PROPP;
1034* //if ( isset($options["PROPFIND"]) ) {
1035*   // Fetch some information about the events in that calendar
1036* //  $cal->SetDepth(1);
1037* //  $folder_xml = $cal->DoXMLRequest("PROPFIND", $xmlC);
1038* //  print_r( $folder_xml);
1039* //}
1040*
1041* // Fetch all events for February
1042* $events = $cal->GetEvents("20090201T000000Z","20090301T000000Z");
1043* foreach ( $events as $k => $event ) {
1044*     print_r($event['data']);
1045*     print "\n---------------------------------------------\n";
1046* }
1047*
1048* //*******************************
1049* //*******************************
1050*/
Note: See TracBrowser for help on using the repository browser.