~aw/ubuntu/lucid/davical/trunk

« back to all changes in this revision

Viewing changes to inc/caldav-client.php

  • Committer: Bazaar Package Importer
  • Author(s): Andrew McMillan
  • Date: 2009-04-11 00:08:31 UTC
  • Revision ID: james.westby@ubuntu.com-20090411000831-zc97ye8hni3ozfzm
Tags: 0.9.6.3
* General bug fixing.
* Added Italian locale from Alessandro De Zorzi
* Adjust timezone handling in regression testing (Markus Warg).
* Many fixes to caldav-client from Michael Rasmussen.
* Update caldav-client to use direct socket IO from Andres Obrero.
* Added dummy handler for POST CANCEL events for iCal compatibilty (Wolfgang Herget).
* Support usernames with spaces or punctuation.
* Correct errors in handling confidential events.
* Improved response to GET for calendar with a single event.
* Documentation corrections.
* Remove the misguided hide_todo configuration option.
* Fix a bug in hiding alarms.
* Deprecate 'collections_always_exist' config option and restrict it's scope.
* Updated French translation.
* Updated relational integrity constraints.
* Fix database versioning.

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
* A Class for connecting to a caldav server
4
4
*
5
5
* @package   awl
 
6
* removed curl - now using fsockopen
 
7
* changed 2009 by Andres Obrero - Switzerland andres@obrero.ch
 
8
*
6
9
* @subpackage   caldav
7
10
* @author Andrew McMillan <debian@mcmillan.net.nz>
8
11
* @copyright Andrew McMillan
9
12
* @license   http://gnu.org/copyleft/gpl.html GNU GPL v2
10
13
*/
11
14
 
12
 
/**
13
 
* I bet you find this hard to believe, but having to write this hack really
14
 
* annoys the crap out of me.  WTF!  Why does PHP/Curl not have a function which
15
 
* simply accepts a string as what the request will contain.  Oh no.  They only
16
 
* think of "POST" and "PUT a file".  Crap.
17
 
*
18
 
* So the following PoS code accepts that it will be called, and asked for $length
19
 
* bites of the $fd (which we ignore, because we get it all from the $_...data variable)
20
 
* and so it will eat it's way through the data.
21
 
*/
22
 
$__curl_read_callback_pos = 0;
23
 
$__curl_read_callback_data = "";
24
 
function __curl_init_callback( $data ) {
25
 
  global $__curl_read_callback_pos, $__curl_read_callback_data;
26
 
  $__curl_read_callback_pos = 0;
27
 
  $__curl_read_callback_data = $data;
28
 
}
29
 
 
30
 
/**
31
 
* As documented in the comments on this page(!)
32
 
*    http://nz2.php.net/curl_setopt
33
 
*/
34
 
function __curl_read_callback( $ch, $fd, $length) {
35
 
  global $__curl_read_callback_pos, $__curl_read_callback_data;
36
 
 
37
 
  if ( $__curl_read_callback_pos < 0 ) {
38
 
    unset($fd);
39
 
    return "";
40
 
  }
41
 
 
42
 
  $answer = substr($__curl_read_callback_data, $__curl_read_callback_pos, $length );
43
 
  if ( strlen($answer) < $length ) $__curl_read_callback_pos = -1;
44
 
  else $__curl_read_callback_pos += $length;
45
 
 
46
 
  return $answer;
47
 
}
48
 
 
49
15
 
50
16
/**
51
17
* A class for accessing DAViCal via CalDAV, as a client
54
20
*/
55
21
class CalDAVClient {
56
22
  /**
57
 
  * Server, username, password, calendar, $entry
 
23
  * Server, username, password, calendar
58
24
  *
59
25
  * @var string
60
26
  */
61
 
  var $base_url, $user, $pass, $calendar, $entry;
 
27
  var $base_url, $user, $pass, $calendar, $entry, $protocol, $server, $port;
62
28
 
63
29
  /**
64
30
  * The useragent which is send to the caldav server
69
35
 
70
36
  var $headers = array();
71
37
  var $body = "";
72
 
 
73
 
  /**
74
 
  * Our cURL connection
75
 
  *
76
 
  * @var resource
77
 
  */
78
 
  var $curl;
79
 
 
 
38
  var $requestMethod = "GET";
 
39
  var $httpRequest = ""; // for debugging http headers sent
 
40
  var $xmlRequest = ""; // for debugging xml sent
 
41
  var $httpResponse = ""; // for debugging http headers received
 
42
  var $xmlResponse = ""; // for debugging xml received
80
43
 
81
44
  /**
82
45
  * Constructor, initialises the class
87
50
  * @param string $calendar  The name of the calendar (not currently used)
88
51
  */
89
52
  function CalDAVClient( $base_url, $user, $pass, $calendar ) {
90
 
    $this->base_url = $base_url;
91
53
    $this->user = $user;
92
54
    $this->pass = $pass;
93
55
    $this->calendar = $calendar;
94
 
 
95
 
    $this->curl = curl_init();
96
 
    curl_setopt($this->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
97
 
    curl_setopt($this->curl, CURLOPT_USERPWD, "$user:$pass" );
98
 
    curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true );
99
 
    curl_setopt($this->curl, CURLOPT_BINARYTRANSFER, true );
100
 
 
101
 
    $this->headers[] = array();
102
 
 
 
56
    $this->headers = array();
 
57
 
 
58
    if ( preg_match( '#^(https?)://([a-z0-9.-]+)(:([0-9]+))?(/.*)$#', $base_url, $matches ) ) {
 
59
      $this->server = $matches[2];
 
60
      $this->base_url = $matches[5];
 
61
      if ( $matches[1] == 'https' ) {
 
62
        $this->protocol = 'ssl';
 
63
        $this->port = 443;
 
64
      }
 
65
      else {
 
66
        $this->protocol = 'tcp';
 
67
        $this->port = 80;
 
68
      }
 
69
      if ( $matches[4] != '' ) {
 
70
        $this->port = intval($matches[4]);
 
71
      }
 
72
    }
 
73
    else {
 
74
      trigger_error("Invalid URL: '".$base_url."'", E_USER_ERROR);
 
75
    }
103
76
  }
104
77
 
105
78
  /**
140
113
    $this->headers[] = "Content-type: $type";
141
114
  }
142
115
 
 
116
  /**
 
117
  * Split response into httpResponse and xmlResponse
 
118
  *
 
119
  * @param string Response from server
 
120
   */
 
121
  function ParseResponse( $response ) {
 
122
      $pos = strpos($response, '<?xml');
 
123
      if ($pos == false) {
 
124
        $this->httpResponse = trim($response);
 
125
      }
 
126
      else {
 
127
        $this->httpResponse = trim(substr($response, 0, $pos));
 
128
        $this->xmlResponse = trim(substr($response, $pos));
 
129
      }
 
130
  }
 
131
 
 
132
  /**
 
133
   * Output http request headers
 
134
   *
 
135
   * @return HTTP headers
 
136
   */
 
137
  function GetHttpRequest() {
 
138
      return $this->httpRequest;
 
139
  }
 
140
  /**
 
141
   * Output http response headers
 
142
   *
 
143
   * @return HTTP headers
 
144
   */
 
145
  function GetHttpResponse() {
 
146
      return $this->httpResponse;
 
147
  }
 
148
  /**
 
149
   * Output xml request
 
150
   *
 
151
   * @return raw xml
 
152
   */
 
153
  function GetXmlRequest() {
 
154
      return $this->xmlRequest;
 
155
  }
 
156
  /**
 
157
   * Output xml response
 
158
   *
 
159
   * @return raw xml
 
160
   */
 
161
  function GetXmlResponse() {
 
162
      return $this->xmlResponse;
 
163
  }
143
164
 
144
165
  /**
145
166
  * Send a request to the server
149
170
  * @return string The content of the response from the server
150
171
  */
151
172
  function DoRequest( $relative_url = "" ) {
152
 
 
153
 
    curl_setopt($this->curl, CURLOPT_URL, $this->base_url . $relative_url );
154
 
    curl_setopt($this->curl, CURLOPT_USERAGENT, $this->user_agent );
155
 
    curl_setopt($this->curl, CURLOPT_HTTPHEADER, $this->headers );
156
 
 
157
 
    /**
158
 
    * So we don't get annoyed at self-signed certificates.  Should be a setup
159
 
    * configuration thing really.
160
 
    */
161
 
    curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false );
162
 
 
163
 
    $bodylen = strlen($this->body);
164
 
    if ( $bodylen > 0 ) {
165
 
      /**
166
 
      * Call our magic write the data function.  You'd think there would be a
167
 
      * simple setopt call where we could set the data to be written, but no,
168
 
      * we have to pass a function, which passes the data.
169
 
      */
170
 
      curl_setopt($this->curl, CURLOPT_UPLOAD, true );
171
 
      __curl_init_callback($this->body);
172
 
      curl_setopt($this->curl, CURLOPT_INFILESIZE, $bodylen );
173
 
      curl_setopt($this->curl, CURLOPT_READFUNCTION, '__curl_read_callback' );
 
173
    if(!defined("_FSOCK_TIMEOUT")){ define("_FSOCK_TIMEOUT", 10); }
 
174
    $headers = array();
 
175
 
 
176
    $headers[] = $this->requestMethod." ". $this->base_url . $relative_url . " HTTP/1.1";
 
177
    $headers[] = "Authorization: Basic ".base64_encode($this->user .":". $this->pass );
 
178
    $headers[] = "Host: ".$this->server .":".$this->port;
 
179
 
 
180
    foreach( $this->headers as $ii => $head ) {
 
181
      $headers[] = $head;
174
182
    }
175
 
 
176
 
    $this->response = curl_exec($this->curl);
177
 
    $this->resultcode = curl_getinfo( $this->curl, CURLINFO_HTTP_CODE);
178
 
 
179
 
    $this->headers[] = array();  // reset the headers array for our next request
180
 
 
181
 
    return $this->response;
 
183
    $headers[] = "Content-Length: " . strlen($this->body);
 
184
    $headers[] = "User-Agent: " . $this->user_agent;
 
185
    $headers[] = 'Connection: close';
 
186
    $this->httpRequest = join("\r\n",$headers);
 
187
    $this->xmlRequest = $this->body;
 
188
 
 
189
    $fip = fsockopen( $this->protocol . '://' . $this->server, $this->port, $errno, $errstr, _FSOCK_TIMEOUT); //error handling?
 
190
    if ( !(get_resource_type($fip) == 'stream') ) return false;
 
191
    if ( !fwrite($fip, $this->httpRequest."\r\n\r\n".$this->body) ) { fclose($fip); return false; }
 
192
    $rsp = "";
 
193
    while( !feof($fip) ) { $rsp .= fgets($fip,8192); }
 
194
    fclose($fip);
 
195
 
 
196
    $this->headers = array();  // reset the headers array for our next request
 
197
    $this->ParseResponse($rsp);
 
198
    return $rsp;
182
199
  }
183
200
 
184
201
 
190
207
  * @return array The allowed options
191
208
  */
192
209
  function DoOptionsRequest( $relative_url = "" ) {
193
 
    curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, "OPTIONS" );
 
210
    $this->requestMethod = "OPTIONS";
194
211
    $this->body = "";
195
 
    curl_setopt($this->curl, CURLOPT_HEADER, true);
196
212
    $headers = $this->DoRequest($relative_url);
197
213
    $options_header = preg_replace( '/^.*Allow: ([a-z, ]+)\r?\n.*/is', '$1', $headers );
198
214
    $options = array_flip( preg_split( '/[, ]+/', $options_header ));
212
228
  */
213
229
  function DoXMLRequest( $request_method, $xml, $relative_url = '' ) {
214
230
    $this->body = $xml;
215
 
 
216
 
    curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $request_method );
217
 
    curl_setopt($this->curl, CURLOPT_HEADER, false);
 
231
    $this->requestMethod = $request_method;
218
232
    $this->SetContentType("text/xml");
219
233
    return $this->DoRequest($relative_url);
220
234
  }
228
242
  */
229
243
  function DoGETRequest( $relative_url ) {
230
244
    $this->body = "";
231
 
    curl_setopt($this->curl, CURLOPT_HTTPGET, true);
232
 
    curl_setopt($this->curl, CURLOPT_HEADER, false);
233
 
    $response = $this->DoRequest( $relative_url );
 
245
    $this->requestMethod = "GET";
 
246
    return $this->DoRequest( $relative_url );
234
247
  }
235
248
 
236
249
 
246
259
  function DoPUTRequest( $relative_url, $icalendar, $etag = null ) {
247
260
    $this->body = $icalendar;
248
261
 
249
 
    curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, "PUT" );
250
 
    curl_setopt($this->curl, CURLOPT_HEADER, true);
 
262
    $this->requestMethod = "PUT";
251
263
    if ( $etag != null ) {
252
264
      $this->SetMatch( ($etag != '*'), $etag );
253
265
    }
274
286
  function DoDELETERequest( $relative_url, $etag = null ) {
275
287
    $this->body = "";
276
288
 
277
 
    curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, "DELETE" );
278
 
    curl_setopt($this->curl, CURLOPT_HEADER, true);
 
289
    $this->requestMethod = "DELETE";
279
290
    if ( $etag != null ) {
280
291
      $this->SetMatch( true, $etag );
281
292
    }
298
309
  *               be an array with 'href', 'etag' and 'data' elements, corresponding to the URL, the server-supplied
299
310
  *               etag (which only varies when the data changes) and the calendar data in iCalendar format.
300
311
  */
301
 
  function DoCalendarQuery( $filter, $relative_url = '', $reponse_type = 'data' ) {
 
312
  function DoCalendarQuery( $filter, $relative_url = '' ) {
302
313
 
303
314
    $xml = <<<EOXML
304
315
<?xml version="1.0" encoding="utf-8" ?>
305
 
<calendar-query xmlns:D="DAV:" xmlns="urn:ietf:params:xml:ns:caldav">
 
316
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
306
317
  <D:prop>
307
 
    <calendar-data/>
 
318
    <C:calendar-data/>
308
319
    <D:getetag/>
309
320
  </D:prop>$filter
310
 
</calendar-query>
 
321
</C:calendar-query>
311
322
EOXML;
312
323
 
313
 
    $this->SetDepth("1");
314
324
    $this->DoXMLRequest( 'REPORT', $xml, $relative_url );
315
325
    $xml_parser = xml_parser_create_ns('UTF-8');
316
326
    $this->xml_tags = array();
317
327
    xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
318
 
    xml_parse_into_struct( $xml_parser, $this->response, $this->xml_tags );
 
328
    xml_parse_into_struct( $xml_parser, $this->xmlResponse, $this->xml_tags );
319
329
    xml_parser_free($xml_parser);
320
330
 
321
331
    $report = array();
322
 
    foreach( $this->xml_tags AS $k => $v ) {
 
332
    foreach( $this->xml_tags as $k => $v ) {
323
333
      switch( $v['tag'] ) {
324
334
        case 'DAV::RESPONSE':
325
335
          if ( $v['type'] == 'open' ) {
337
347
          break;
338
348
        case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-DATA':
339
349
          $response['data'] = $v['value'];
340
 
          if ( $report_type != 'data' ) $response[$report_type] = $v['value'];  // deprecated - will be removed.  Just use 'data' please :-)
341
350
          break;
342
351
      }
343
352
    }
358
367
  *
359
368
  * @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
360
369
  */
361
 
  function GetEvents( $start, $finish, $relative_url = '' ) {
 
370
  function GetEvents( $start = null, $finish = null, $relative_url = '' ) {
362
371
    $filter = "";
363
 
    if ( $start && $finish ) {
364
 
      $filter = <<<EOFILTER
 
372
    if ( isset($start) && isset($finish) )
 
373
        $range = "<C:time-range start=\"$start\" end=\"$finish\"/>";
 
374
    else
 
375
        $range = '';
365
376
 
366
 
  <filter>
367
 
    <comp-filter name="VCALENDAR">
368
 
      <comp-filter name="VEVENT">
369
 
        <time-range start="$start" end="$finish"/>
370
 
      </comp-filter>
371
 
    </comp-filter>
372
 
  </filter>
 
377
    $filter = <<<EOFILTER
 
378
  <C:filter>
 
379
    <C:comp-filter name="VCALENDAR">
 
380
      <C:comp-filter name="VEVENT">
 
381
        $range
 
382
      </C:comp-filter>
 
383
    </C:comp-filter>
 
384
  </C:filter>
373
385
EOFILTER;
374
 
    }
375
386
 
376
 
    return DoCalendarQuery($filter, $relative_url);
 
387
    return $this->DoCalendarQuery($filter, $relative_url);
377
388
  }
378
389
 
379
390
 
419
430
  </C:filter>
420
431
EOFILTER;
421
432
 
422
 
    return DoCalendarQuery($filter, $relative_url);
 
433
    return $this->DoCalendarQuery($filter, $relative_url);
423
434
  }
424
435
 
425
436
 
437
448
      $filter = <<<EOFILTER
438
449
  <C:filter>
439
450
    <C:comp-filter name="VCALENDAR">
440
 
          <C:comp-filter name="VTODO">
 
451
          <C:comp-filter name="VEVENT">
441
452
                <C:prop-filter name="UID">
442
453
                        <C:text-match icollation="i;octet">$uid</C:text-match>
443
454
                </C:prop-filter>
447
458
EOFILTER;
448
459
    }
449
460
 
450
 
    return DoCalendarQuery($filter, $relative_url);
 
461
    return $this->DoCalendarQuery($filter, $relative_url);
451
462
  }
452
463
 
453
464
 
460
471
  * @return string The iCalendar of the calendar entry
461
472
  */
462
473
  function GetEntryByHref( $href, $relative_url = '' ) {
463
 
    return DoGETRequest( $relative_url . $href );
 
474
    return $this->DoGETRequest( $relative_url . $href );
464
475
  }
465
476
 
466
477
}
467
478
 
468
479
/**
469
480
* Usage example
470
 
 
471
 
$cal = new CalDAVClient( "http://calendar.example.com/caldav.php/username/calendar/", "username", "password", "calendar" );
472
 
$options = $cal->DoOptionsRequest();
473
 
if ( isset($options["PROPFIND"]) ) {
474
 
  // Fetch some information about the events in that calendar
475
 
  $cal->SetDepth(1);
476
 
  $folder_xml = $cal->DoXMLRequest("PROPFIND", '<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><getcontentlength/><getcontenttype/><resourcetype/><getetag/></prop></propfind>' );
477
 
}
478
 
// Fetch all events for February
479
 
$events = $cal->GetEvents("20070101T000000Z","20070201T000000Z");
480
 
foreach ( $events AS $k => $event ) {
481
 
  do_something_with_event_data( $event['data'] );
482
 
}
 
481
*
 
482
* $cal = new CalDAVClient( "http://calendar.example.com/caldav.php/username/calendar/", "username", "password", "calendar" );
 
483
* $options = $cal->DoOptionsRequest();
 
484
* if ( isset($options["PROPFIND"]) ) {
 
485
*   // Fetch some information about the events in that calendar
 
486
*   $cal->SetDepth(1);
 
487
*   $folder_xml = $cal->DoXMLRequest("PROPFIND", '<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><getcontentlength/><getcontenttype/><resourcetype/><getetag/></prop></propfind>' );
 
488
* }
 
489
* // Fetch all events for February
 
490
* $events = $cal->GetEvents("20070101T000000Z","20070201T000000Z");
 
491
* foreach ( $events AS $k => $event ) {
 
492
*   do_something_with_event_data( $event['data'] );
 
493
* }
 
494
* $acc = array();
 
495
* $acc["google"] = array(
 
496
* "user"=>"kunsttherapie@gmail.com",
 
497
* "pass"=>"xxxxx",
 
498
* "server"=>"ssl://www.google.com",
 
499
* "port"=>"443",
 
500
* "uri"=>"https://www.google.com/calendar/dav/kunsttherapie@gmail.com/events/",
 
501
* );
 
502
*
 
503
* $acc["davical"] = array(
 
504
* "user"=>"some_user",
 
505
* "pass"=>"big secret",
 
506
* "server"=>"calendar.foo.bar",
 
507
* "port"=>"80",
 
508
* "uri"=>"http://calendar.foo.bar/caldav.php/some_user/home/",
 
509
* );
 
510
* //*******************************
 
511
*
 
512
* $account = $acc["davical"];
 
513
*
 
514
* //*******************************
 
515
* $cal = new CalDAVClient( $account["uri"], $account["user"], $account["pass"], "", $account["server"], $account["port"] );
 
516
* $options = $cal->DoOptionsRequest();
 
517
* print_r($options);
 
518
*
 
519
* //*******************************
 
520
* //*******************************
 
521
*
 
522
* $xmlC = <<<PROPP
 
523
* <?xml version="1.0" encoding="utf-8" ?>
 
524
* <D:propfind xmlns:D="DAV:" xmlns:C="http://calendarserver.org/ns/">
 
525
*     <D:prop>
 
526
*             <D:displayname />
 
527
*             <C:getctag />
 
528
*             <D:resourcetype />
 
529
*
 
530
*     </D:prop>
 
531
* </D:propfind>
 
532
* PROPP;
 
533
* //if ( isset($options["PROPFIND"]) ) {
 
534
*   // Fetch some information about the events in that calendar
 
535
* //  $cal->SetDepth(1);
 
536
* //  $folder_xml = $cal->DoXMLRequest("PROPFIND", $xmlC);
 
537
* //  print_r( $folder_xml);
 
538
* //}
 
539
*
 
540
* // Fetch all events for February
 
541
* $events = $cal->GetEvents("20090201T000000Z","20090301T000000Z");
 
542
* foreach ( $events as $k => $event ) {
 
543
*     print_r($event['data']);
 
544
*     print "\n---------------------------------------------\n";
 
545
* }
 
546
*
 
547
* //*******************************
 
548
* //*******************************
483
549
*/