DAViCal
 All Classes Namespaces Functions Variables Pages
caldav-client.php
1 <?php
20 class CalDAVClient {
26  var $base_url, $user, $pass, $calendar, $entry, $protocol, $server, $port;
27 
33  var $user_agent = 'DAViCalClient';
34 
35  var $headers = array();
36  var $body = "";
37  var $requestMethod = "GET";
38  var $httpRequest = ""; // for debugging http headers sent
39  var $xmlRequest = ""; // for debugging xml sent
40  var $httpResponse = ""; // for debugging http headers received
41  var $xmlResponse = ""; // for debugging xml received
42 
51  function CalDAVClient( $base_url, $user, $pass, $calendar = '' ) {
52  $this->user = $user;
53  $this->pass = $pass;
54  $this->calendar = $calendar;
55  $this->headers = array();
56 
57  if ( preg_match( '#^(https?)://([a-z0-9.-]+)(:([0-9]+))?(/.*)$#', $base_url, $matches ) ) {
58  $this->server = $matches[2];
59  $this->base_url = $matches[5];
60  if ( $matches[1] == 'https' ) {
61  $this->protocol = 'ssl';
62  $this->port = 443;
63  }
64  else {
65  $this->protocol = 'tcp';
66  $this->port = 80;
67  }
68  if ( $matches[4] != '' ) {
69  $this->port = intval($matches[4]);
70  }
71  }
72  else {
73  trigger_error("Invalid URL: '".$base_url."'", E_USER_ERROR);
74  }
75  }
76 
83  function SetMatch( $match, $etag = '*' ) {
84  $this->headers[] = sprintf( "%s-Match: %s", ($match ? "If" : "If-None"), $etag);
85  }
86 
87  /*
88  * Add a Depth: header. Valid values are 0, 1 or infinity
89  *
90  * @param int $depth The depth, default to infinity
91  */
92  function SetDepth( $depth = '0' ) {
93  $this->headers[] = 'Depth: '. ($depth == '1' ? "1" : ($depth == 'infinity' ? $depth : "0") );
94  }
95 
101  function SetUserAgent( $user_agent = null ) {
102  if ( !isset($user_agent) ) $user_agent = $this->user_agent;
103  $this->user_agent = $user_agent;
104  }
105 
111  function SetContentType( $type ) {
112  $this->headers[] = "Content-type: $type";
113  }
114 
120  function ParseResponse( $response ) {
121  $pos = strpos($response, '<?xml');
122  if ($pos === false) {
123  $this->httpResponse = trim($response);
124  }
125  else {
126  $this->httpResponse = trim(substr($response, 0, $pos));
127  $this->xmlResponse = trim(substr($response, $pos));
128  }
129  }
130 
136  function GetHttpRequest() {
137  return $this->httpRequest;
138  }
144  function GetHttpResponse() {
145  return $this->httpResponse;
146  }
152  function GetXmlRequest() {
153  return $this->xmlRequest;
154  }
160  function GetXmlResponse() {
161  return $this->xmlResponse;
162  }
163 
171  function DoRequest( $relative_url = "" ) {
172  if(!defined("_FSOCK_TIMEOUT")){ define("_FSOCK_TIMEOUT", 10); }
173  $headers = array();
174 
175  $headers[] = $this->requestMethod." ". $this->base_url . $relative_url . " HTTP/1.1";
176  $headers[] = "Authorization: Basic ".base64_encode($this->user .":". $this->pass );
177  $headers[] = "Host: ".$this->server .":".$this->port;
178 
179  foreach( $this->headers as $ii => $head ) {
180  $headers[] = $head;
181  }
182  $headers[] = "Content-Length: " . strlen($this->body);
183  $headers[] = "User-Agent: " . $this->user_agent;
184  $headers[] = 'Connection: close';
185  $this->httpRequest = join("\r\n",$headers);
186  $this->xmlRequest = $this->body;
187 
188  $fip = fsockopen( $this->protocol . '://' . $this->server, $this->port, $errno, $errstr, _FSOCK_TIMEOUT); //error handling?
189  if ( !(get_resource_type($fip) == 'stream') ) return false;
190  if ( !fwrite($fip, $this->httpRequest."\r\n\r\n".$this->body) ) { fclose($fip); return false; }
191  $rsp = "";
192  while( !feof($fip) ) { $rsp .= fgets($fip,8192); }
193  fclose($fip);
194 
195  $this->headers = array(); // reset the headers array for our next request
196  $this->ParseResponse($rsp);
197  return $rsp;
198  }
199 
200 
208  function DoOptionsRequest( $relative_url = "" ) {
209  $this->requestMethod = "OPTIONS";
210  $this->body = "";
211  $headers = $this->DoRequest($relative_url);
212  $options_header = preg_replace( '/^.*Allow: ([a-z, ]+)\r?\n.*/is', '$1', $headers );
213  $options = array_flip( preg_split( '/[, ]+/', $options_header ));
214  return $options;
215  }
216 
217 
218 
228  function DoXMLRequest( $request_method, $xml, $relative_url = '' ) {
229  $this->body = $xml;
230  $this->requestMethod = $request_method;
231  $this->SetContentType("text/xml");
232  return $this->DoRequest($relative_url);
233  }
234 
235 
236 
242  function DoGETRequest( $relative_url ) {
243  $this->body = "";
244  $this->requestMethod = "GET";
245  return $this->DoRequest( $relative_url );
246  }
247 
248 
258  function DoPUTRequest( $relative_url, $icalendar, $etag = null ) {
259  $this->body = $icalendar;
260 
261  $this->requestMethod = "PUT";
262  if ( $etag != null ) {
263  $this->SetMatch( ($etag != '*'), $etag );
264  }
265  $this->SetContentType("text/icalendar");
266  $headers = $this->DoRequest($relative_url);
267 
272  $etag = preg_replace( '/^.*Etag: "?([^"\r\n]+)"?\r?\n.*/is', '$1', $headers );
273  return $etag;
274  }
275 
276 
285  function DoDELETERequest( $relative_url, $etag = null ) {
286  $this->body = "";
287 
288  $this->requestMethod = "DELETE";
289  if ( $etag != null ) {
290  $this->SetMatch( true, $etag );
291  }
292  $this->DoRequest($relative_url);
293  return $this->resultcode;
294  }
295 
296 
310  function DoCalendarQuery( $filter, $relative_url = '' ) {
311 
312  $xml = <<<EOXML
313 <?xml version="1.0" encoding="utf-8" ?>
314 <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
315  <D:prop>
316  <C:calendar-data/>
317  <D:getetag/>
318  </D:prop>$filter
319 </C:calendar-query>
320 EOXML;
321 
322  $this->DoXMLRequest( 'REPORT', $xml, $relative_url );
323  $xml_parser = xml_parser_create_ns('UTF-8');
324  $this->xml_tags = array();
325  xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
326  xml_parse_into_struct( $xml_parser, $this->xmlResponse, $this->xml_tags );
327  xml_parser_free($xml_parser);
328 
329  $report = array();
330  foreach( $this->xml_tags as $k => $v ) {
331  switch( $v['tag'] ) {
332  case 'DAV::RESPONSE':
333  if ( $v['type'] == 'open' ) {
334  $response = array();
335  }
336  elseif ( $v['type'] == 'close' ) {
337  $report[] = $response;
338  }
339  break;
340  case 'DAV::HREF':
341  $response['href'] = basename( $v['value'] );
342  break;
343  case 'DAV::GETETAG':
344  $response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
345  break;
346  case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-DATA':
347  $response['data'] = $v['value'];
348  break;
349  }
350  }
351  return $report;
352  }
353 
354 
368  function GetEvents( $start = null, $finish = null, $relative_url = '' ) {
369  $filter = "";
370  if ( isset($start) && isset($finish) )
371  $range = "<C:time-range start=\"$start\" end=\"$finish\"/>";
372  else
373  $range = '';
374 
375  $filter = <<<EOFILTER
376  <C:filter>
377  <C:comp-filter name="VCALENDAR">
378  <C:comp-filter name="VEVENT">
379  $range
380  </C:comp-filter>
381  </C:comp-filter>
382  </C:filter>
383 EOFILTER;
384 
385  return $this->DoCalendarQuery($filter, $relative_url);
386  }
387 
388 
404  function GetTodos( $start, $finish, $completed = false, $cancelled = false, $relative_url = "" ) {
405 
406  if ( $start && $finish ) {
407 $time_range = <<<EOTIME
408  <C:time-range start="$start" end="$finish"/>
409 EOTIME;
410  }
411 
412  // Warning! May contain traces of double negatives...
413  $neg_cancelled = ( $cancelled === true ? "no" : "yes" );
414  $neg_completed = ( $cancelled === true ? "no" : "yes" );
415 
416  $filter = <<<EOFILTER
417  <C:filter>
418  <C:comp-filter name="VCALENDAR">
419  <C:comp-filter name="VTODO">
420  <C:prop-filter name="STATUS">
421  <C:text-match negate-condition="$neg_completed">COMPLETED</C:text-match>
422  </C:prop-filter>
423  <C:prop-filter name="STATUS">
424  <C:text-match negate-condition="$neg_cancelled">CANCELLED</C:text-match>
425  </C:prop-filter>$time_range
426  </C:comp-filter>
427  </C:comp-filter>
428  </C:filter>
429 EOFILTER;
430 
431  return $this->DoCalendarQuery($filter, $relative_url);
432  }
433 
434 
443  function GetEntryByUid( $uid, $relative_url = '' ) {
444  $filter = "";
445  if ( $uid ) {
446  $filter = <<<EOFILTER
447  <C:filter>
448  <C:comp-filter name="VCALENDAR">
449  <C:comp-filter name="VEVENT">
450  <C:prop-filter name="UID">
451  <C:text-match icollation="i;octet">$uid</C:text-match>
452  </C:prop-filter>
453  </C:comp-filter>
454  </C:comp-filter>
455  </C:filter>
456 EOFILTER;
457  }
458 
459  return $this->DoCalendarQuery($filter, $relative_url);
460  }
461 
462 
471  function GetEntryByHref( $href, $relative_url = '' ) {
472  return $this->DoGETRequest( $relative_url . $href );
473  }
474 
475 }
476 
GetTodos($start, $finish, $completed=false, $cancelled=false, $relative_url="")
GetEntryByHref($href, $relative_url= '')
GetEntryByUid($uid, $relative_url= '')
DoRequest($url=null)
SetMatch($match, $etag= '*')
DoDELETERequest($relative_url, $etag=null)
SetDepth($depth= '0')
DoCalendarQuery($filter, $relative_url= '')
DoXMLRequest($request_method, $xml, $relative_url= '')
CalDAVClient($base_url, $user, $pass, $calendar= '')
DoCalendarQuery($filter, $url= '')
SetContentType($type)
DoPUTRequest($relative_url, $icalendar, $etag=null)
ParseResponse($response)
DoGETRequest($relative_url)
DoOptionsRequest($relative_url="")
SetUserAgent($user_agent=null)
DoXMLRequest($request_method, $xml, $url=null)
GetEvents($start=null, $finish=null, $relative_url= '')
DoRequest($relative_url="")