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

Revision 6675, 32.4 KB checked in by jefferson, 12 years ago (diff)

Ticket #2635 - Commit Correcao da ER no arquivo que geraram inconsistencias na importacao do caldav

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