3
3
* A Class for connecting to a caldav server
6
* removed curl - now using fsockopen
7
* changed 2009 by Andres Obrero - Switzerland andres@obrero.ch
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
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.
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.
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;
31
* As documented in the comments on this page(!)
32
* http://nz2.php.net/curl_setopt
34
function __curl_read_callback( $ch, $fd, $length) {
35
global $__curl_read_callback_pos, $__curl_read_callback_data;
37
if ( $__curl_read_callback_pos < 0 ) {
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;
51
17
* A class for accessing DAViCal via CalDAV, as a client
55
21
class CalDAVClient {
57
* Server, username, password, calendar, $entry
23
* Server, username, password, calendar
61
var $base_url, $user, $pass, $calendar, $entry;
27
var $base_url, $user, $pass, $calendar, $entry, $protocol, $server, $port;
64
30
* The useragent which is send to the caldav server
87
50
* @param string $calendar The name of the calendar (not currently used)
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;
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 );
101
$this->headers[] = array();
56
$this->headers = array();
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';
66
$this->protocol = 'tcp';
69
if ( $matches[4] != '' ) {
70
$this->port = intval($matches[4]);
74
trigger_error("Invalid URL: '".$base_url."'", E_USER_ERROR);
140
113
$this->headers[] = "Content-type: $type";
117
* Split response into httpResponse and xmlResponse
119
* @param string Response from server
121
function ParseResponse( $response ) {
122
$pos = strpos($response, '<?xml');
124
$this->httpResponse = trim($response);
127
$this->httpResponse = trim(substr($response, 0, $pos));
128
$this->xmlResponse = trim(substr($response, $pos));
133
* Output http request headers
135
* @return HTTP headers
137
function GetHttpRequest() {
138
return $this->httpRequest;
141
* Output http response headers
143
* @return HTTP headers
145
function GetHttpResponse() {
146
return $this->httpResponse;
153
function GetXmlRequest() {
154
return $this->xmlRequest;
157
* Output xml response
161
function GetXmlResponse() {
162
return $this->xmlResponse;
145
166
* Send a request to the server
149
170
* @return string The content of the response from the server
151
172
function DoRequest( $relative_url = "" ) {
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 );
158
* So we don't get annoyed at self-signed certificates. Should be a setup
159
* configuration thing really.
161
curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false );
163
$bodylen = strlen($this->body);
164
if ( $bodylen > 0 ) {
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.
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); }
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;
180
foreach( $this->headers as $ii => $head ) {
176
$this->response = curl_exec($this->curl);
177
$this->resultcode = curl_getinfo( $this->curl, CURLINFO_HTTP_CODE);
179
$this->headers[] = array(); // reset the headers array for our next request
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;
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; }
193
while( !feof($fip) ) { $rsp .= fgets($fip,8192); }
196
$this->headers = array(); // reset the headers array for our next request
197
$this->ParseResponse($rsp);
190
207
* @return array The allowed options
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 ));
246
259
function DoPUTRequest( $relative_url, $icalendar, $etag = null ) {
247
260
$this->body = $icalendar;
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 );
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.
301
function DoCalendarQuery( $filter, $relative_url = '', $reponse_type = 'data' ) {
312
function DoCalendarQuery( $filter, $relative_url = '' ) {
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">
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);
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' ) {
359
368
* @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
361
function GetEvents( $start, $finish, $relative_url = '' ) {
370
function GetEvents( $start = null, $finish = null, $relative_url = '' ) {
363
if ( $start && $finish ) {
364
$filter = <<<EOFILTER
372
if ( isset($start) && isset($finish) )
373
$range = "<C:time-range start=\"$start\" end=\"$finish\"/>";
367
<comp-filter name="VCALENDAR">
368
<comp-filter name="VEVENT">
369
<time-range start="$start" end="$finish"/>
377
$filter = <<<EOFILTER
379
<C:comp-filter name="VCALENDAR">
380
<C:comp-filter name="VEVENT">
376
return DoCalendarQuery($filter, $relative_url);
387
return $this->DoCalendarQuery($filter, $relative_url);
460
471
* @return string The iCalendar of the calendar entry
462
473
function GetEntryByHref( $href, $relative_url = '' ) {
463
return DoGETRequest( $relative_url . $href );
474
return $this->DoGETRequest( $relative_url . $href );
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
476
$folder_xml = $cal->DoXMLRequest("PROPFIND", '<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><getcontentlength/><getcontenttype/><resourcetype/><getetag/></prop></propfind>' );
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
* $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
487
* $folder_xml = $cal->DoXMLRequest("PROPFIND", '<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><getcontentlength/><getcontenttype/><resourcetype/><getetag/></prop></propfind>' );
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'] );
495
* $acc["google"] = array(
496
* "user"=>"kunsttherapie@gmail.com",
498
* "server"=>"ssl://www.google.com",
500
* "uri"=>"https://www.google.com/calendar/dav/kunsttherapie@gmail.com/events/",
503
* $acc["davical"] = array(
504
* "user"=>"some_user",
505
* "pass"=>"big secret",
506
* "server"=>"calendar.foo.bar",
508
* "uri"=>"http://calendar.foo.bar/caldav.php/some_user/home/",
510
* //*******************************
512
* $account = $acc["davical"];
514
* //*******************************
515
* $cal = new CalDAVClient( $account["uri"], $account["user"], $account["pass"], "", $account["server"], $account["port"] );
516
* $options = $cal->DoOptionsRequest();
519
* //*******************************
520
* //*******************************
523
* <?xml version="1.0" encoding="utf-8" ?>
524
* <D:propfind xmlns:D="DAV:" xmlns:C="http://calendarserver.org/ns/">
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);
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";
547
* //*******************************
548
* //*******************************