davical/inc/caldav-client.php

490 lines
16 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* A Class for connecting to a caldav server
*
* @package awl
* removed curl - now using fsockopen
* changed 2009 by Andres Obrero - Switzerland andres@obrero.ch
*
* @subpackage caldav
* @author Andrew McMillan <debian@mcmillan.net.nz>
* @copyright Andrew McMillan
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
*/
/**
* A class for accessing DAViCal via CalDAV, as a client
*
* @package awl
*/
class CalDAVClient {
/**
* Server, username, password, calendar
*
* @var string
*/
var $base_url, $user, $pass, $calendar, $entry, $protocol, $server, $port;
/**
* The useragent which is send to the caldav server
*
* @var string
*/
var $user_agent = 'DAViCalClient';
var $headers = array();
var $body = "";
var $requestMethod = "GET";
var $httpRequest = ""; // for debugging what have we sent
/**
* Constructor, initialises the class
*
* @param string $base_url The URL for the calendar server
* @param string $user The name of the user logging in
* @param string $pass The password for that user
* @param string $calendar The name of the calendar (not currently used)
*/
function CalDAVClient( $base_url, $user, $pass, $calendar ) {
$this->base_url = $base_url;
$this->user = $user;
$this->pass = $pass;
$this->calendar = $calendar;
$this->headers = array();
if ( preg_match( '#^(https?)://([a-z0-9.-]+)(:([0-9]+))?/#', $base_url, $matches ) ) {
$this->server = $matches[2];
if ( $matches[1] == 'https' ) {
$this->protocol = 'ssl';
$this->port = 443;
}
else {
$this->protocol = 'http';
$this->port = 80;
}
if ( $matches[4] != '' ) {
$this->port = intval($matches[4]);
}
}
else {
trigger_error("Invalid URL: '".$base_url."'", E_USER_ERROR);
}
}
/**
* Adds an If-Match or If-None-Match header
*
* @param bool $match to Match or Not to Match, that is the question!
* @param string $etag The etag to match / not match against.
*/
function SetMatch( $match, $etag = '*' ) {
$this->headers[] = sprintf( "%s-Match: %s", ($match ? "If" : "If-None"), $etag);
}
/**
* Add a Depth: header. Valid values are 1 or infinity
*
* @param int $depth The depth, default to infinity
*/
function SetDepth( $depth = 'infinity' ) {
$this->headers[] = "Depth: ". ($depth == 1 ? "1" : "infinity" );
}
/**
* Add a Depth: header. Valid values are 1 or infinity
*
* @param int $depth The depth, default to infinity
*/
function SetUserAgent( $user_agent = null ) {
if ( !isset($user_agent) ) $user_agent = $this->user_agent;
$this->user_agent = $user_agent;
}
/**
* Add a Content-type: header.
*
* @param int $type The content type
*/
function SetContentType( $type ) {
$this->headers[] = "Content-type: $type";
}
/**
* Send a request to the server
*
* @param string $relative_url The URL to make the request to, relative to $base_url
*
* @return string The content of the response from the server
*/
function DoRequest( $relative_url = "" ) {
if(!defined("_FSOCK_TIMEOUT")){ define("_FSOCK_TIMEOUT", 10); }
$headers = array();
$headers[] = $this->requestMethod." ". $this->base_url . $relative_url . " HTTP/1.1";
$headers[] = "Authorization: Basic ".base64_encode($this->user .":". $this->pass );
$headers[] = "Host: ".$this->server .": ".$this->port;
foreach( $this->headers as $ii => $head ) {
$headers[] = $head;
}
$headers[] = "Content-Length: " . strlen($this->body);
$headers[] = "User-Agent: " . $this->user_agent;
$headers[] = 'Connection: close';
$this->httpRequest = join("\r\n",$headers) . "\r\n\r\n" . $this->body;
$fip = fsockopen( $this->protocol . '://' . $this->server, $this->port, $errno, $errstr, _FSOCK_TIMEOUT); //error handling?
if ( !(get_resource_type($fip) == 'stream') ) return false;
if ( !fwrite($fip, $this->httpRequest) ) { fclose($fip); return false; }
$rsp = "";
while( !feof($fip) ) { $rsp .= fgets($fip,8192); }
fclose($fip);
$this->headers = array(); // reset the headers array for our next request
$this->response = $rsp;
return $this->response;
}
/**
* Send an OPTIONS request to the server
*
* @param string $relative_url The URL to make the request to, relative to $base_url
*
* @return array The allowed options
*/
function DoOptionsRequest( $relative_url = "" ) {
$this->requestMethod = "OPTIONS";
$this->body = "";
$headers = $this->DoRequest($relative_url);
$options_header = preg_replace( '/^.*Allow: ([a-z, ]+)\r?\n.*/is', '$1', $headers );
$options = array_flip( preg_split( '/[, ]+/', $options_header ));
return $options;
}
/**
* Send an XML request to the server (e.g. PROPFIND, REPORT, MKCALENDAR)
*
* @param string $method The method (PROPFIND, REPORT, etc) to use with the request
* @param string $xml The XML to send along with the request
* @param string $relative_url The URL to make the request to, relative to $base_url
*
* @return array An array of the allowed methods
*/
function DoXMLRequest( $request_method, $xml, $relative_url = '' ) {
$this->body = $xml;
$this->requestMethod = $request_method;
$this->SetContentType("text/xml");
return $this->DoRequest($relative_url);
}
/**
* Get a single item from the server.
*
* @param string $relative_url The part of the URL after the calendar
*/
function DoGETRequest( $relative_url ) {
$this->body = "";
$this->requestMethod = "GET";
return $this->DoRequest( $relative_url );
}
/**
* PUT a text/icalendar resource, returning the etag
*
* @param string $relative_url The URL to make the request to, relative to $base_url
* @param string $icalendar The iCalendar resource to send to the server
* @param string $etag The etag of an existing resource to be overwritten, or '*' for a new resource.
*
* @return string The content of the response from the server
*/
function DoPUTRequest( $relative_url, $icalendar, $etag = null ) {
$this->body = $icalendar;
$this->requestMethod = "PUT";
if ( $etag != null ) {
$this->SetMatch( ($etag != '*'), $etag );
}
$this->SetContentType("text/icalendar");
$headers = $this->DoRequest($relative_url);
/**
* RSCDS will always return the real etag on PUT. Other CalDAV servers may need
* more work, but we are assuming we are running against RSCDS in this case.
*/
$etag = preg_replace( '/^.*Etag: "?([^"\r\n]+)"?\r?\n.*/is', '$1', $headers );
return $etag;
}
/**
* DELETE a text/icalendar resource
*
* @param string $relative_url The URL to make the request to, relative to $base_url
* @param string $etag The etag of an existing resource to be deleted, or '*' for any resource at that URL.
*
* @return int The HTTP Result Code for the DELETE
*/
function DoDELETERequest( $relative_url, $etag = null ) {
$this->body = "";
$this->requestMethod = "DELETE";
if ( $etag != null ) {
$this->SetMatch( true, $etag );
}
$this->DoRequest($relative_url);
return $this->resultcode;
}
/**
* Given XML for a calendar query, return an array of the events (/todos) in the
* response. Each event in the array will have a 'href', 'etag' and '$response_type'
* part, where the 'href' is relative to the calendar and the '$response_type' contains the
* definition of the calendar data in iCalendar format.
*
* @param string $filter XML fragment which is the <filter> element of a calendar-query
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
* @param string $report_type Used as a name for the array element containing the calendar data. @deprecated
*
* @return array An array of the relative URLs, etags, and events from the server. Each element of the array will
* be an array with 'href', 'etag' and 'data' elements, corresponding to the URL, the server-supplied
* etag (which only varies when the data changes) and the calendar data in iCalendar format.
*/
function DoCalendarQuery( $filter, $relative_url = '', $reponse_type = 'data' ) {
$xml = <<<EOXML
<?xml version="1.0" encoding="utf-8" ?>
<calendar-query xmlns:D="DAV:" xmlns="urn:ietf:params:xml:ns:caldav">
<D:prop>
<calendar-data/>
<D:getetag/>
</D:prop>$filter
</calendar-query>
EOXML;
$this->SetDepth("1");
$this->DoXMLRequest( 'REPORT', $xml, $relative_url );
$xml_parser = xml_parser_create_ns('UTF-8');
$this->xml_tags = array();
xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
xml_parse_into_struct( $xml_parser, $this->response, $this->xml_tags );
xml_parser_free($xml_parser);
$report = array();
foreach( $this->xml_tags AS $k => $v ) {
switch( $v['tag'] ) {
case 'DAV::RESPONSE':
if ( $v['type'] == 'open' ) {
$response = array();
}
elseif ( $v['type'] == 'close' ) {
$report[] = $response;
}
break;
case 'DAV::HREF':
$response['href'] = basename( $v['value'] );
break;
case 'DAV::GETETAG':
$response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
break;
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-DATA':
$response['data'] = $v['value'];
if ( $report_type != 'data' ) $response[$report_type] = $v['value']; // deprecated - will be removed. Just use 'data' please :-)
break;
}
}
return $report;
}
/**
* Get the events in a range from $start to $finish. The dates should be in the
* format yyyymmddThhmmssZ and should be in GMT. The events are returned as an
* array of event arrays. Each event array will have a 'href', 'etag' and 'event'
* part, where the 'href' is relative to the calendar and the event contains the
* definition of the event in iCalendar format.
*
* @param timestamp $start The start time for the period
* @param timestamp $finish The finish time for the period
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
*
* @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
*/
function GetEvents( $start, $finish, $relative_url = '' ) {
$filter = "";
if ( $start && $finish ) {
$filter = <<<EOFILTER
<filter>
<comp-filter name="VCALENDAR">
<comp-filter name="VEVENT">
<time-range start="$start" end="$finish"/>
</comp-filter>
</comp-filter>
</filter>
EOFILTER;
}
return DoCalendarQuery($filter, $relative_url);
}
/**
* Get the todo's in a range from $start to $finish. The dates should be in the
* format yyyymmddThhmmssZ and should be in GMT. The events are returned as an
* array of event arrays. Each event array will have a 'href', 'etag' and 'event'
* part, where the 'href' is relative to the calendar and the event contains the
* definition of the event in iCalendar format.
*
* @param timestamp $start The start time for the period
* @param timestamp $finish The finish time for the period
* @param boolean $completed Whether to include completed tasks
* @param boolean $cancelled Whether to include cancelled tasks
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
*
* @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
*/
function GetTodos( $start, $finish, $completed = false, $cancelled = false, $relative_url = "" ) {
if ( $start && $finish ) {
$time_range = <<<EOTIME
<C:time-range start="$start" end="$finish"/>
EOTIME;
}
// Warning! May contain traces of double negatives...
$neg_cancelled = ( $cancelled === true ? "no" : "yes" );
$neg_completed = ( $cancelled === true ? "no" : "yes" );
$filter = <<<EOFILTER
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VTODO">
<C:prop-filter name="STATUS">
<C:text-match negate-condition="$neg_completed">COMPLETED</C:text-match>
</C:prop-filter>
<C:prop-filter name="STATUS">
<C:text-match negate-condition="$neg_cancelled">CANCELLED</C:text-match>
</C:prop-filter>$time_range
</C:comp-filter>
</C:comp-filter>
</C:filter>
EOFILTER;
return DoCalendarQuery($filter, $relative_url);
}
/**
* Get the calendar entry by UID
*
* @param uid
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
*
* @return array An array of the relative URL, etag, and calendar data returned from DoCalendarQuery() @see DoCalendarQuery()
*/
function GetEntryByUid( $uid, $relative_url = '' ) {
$filter = "";
if ( $uid ) {
$filter = <<<EOFILTER
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VTODO">
<C:prop-filter name="UID">
<C:text-match icollation="i;octet">$uid</C:text-match>
</C:prop-filter>
</C:comp-filter>
</C:comp-filter>
</C:filter>
EOFILTER;
}
return DoCalendarQuery($filter, $relative_url);
}
/**
* Get the calendar entry by HREF
*
* @param string $href The href from a call to GetEvents or GetTodos etc.
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
*
* @return string The iCalendar of the calendar entry
*/
function GetEntryByHref( $href, $relative_url = '' ) {
return DoGETRequest( $relative_url . $href );
}
}
/**
* Usage example
$cal = new CalDAVClient( "http://calendar.example.com/caldav.php/username/calendar/", "username", "password", "calendar" );
$options = $cal->DoOptionsRequest();
if ( isset($options["PROPFIND"]) ) {
// Fetch some information about the events in that calendar
$cal->SetDepth(1);
$folder_xml = $cal->DoXMLRequest("PROPFIND", '<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><getcontentlength/><getcontenttype/><resourcetype/><getetag/></prop></propfind>' );
}
// Fetch all events for February
$events = $cal->GetEvents("20070101T000000Z","20070201T000000Z");
foreach ( $events AS $k => $event ) {
do_something_with_event_data( $event['data'] );
}
*/
$acc = array();
$acc["google"] = array(
"user"=>"kunsttherapie@gmail.com",
"pass"=>"xxxxx",
"server"=>"ssl://www.google.com",
"port"=>"443",
"uri"=>"https://www.google.com/calendar/dav/kunsttherapie@gmail.com/events/",
);
$acc["davical"] = array(
"user"=>"andres",
"pass"=>"xxxxxx",
"server"=>"calendar.farbraum.biz",
"port"=>"80",
"uri"=>"http://calendar.farbraum.biz/caldav.php/andres/home/",
);
//*******************************
$account = $acc["davical"];
//*******************************
$cal = new CalDAVClient( $account["uri"], $account["user"], $account["pass"], "", $account["server"], $account["port"] );
$options = $cal->DoOptionsRequest();
print_r($options);
//*******************************
//*******************************
$xmlC = <<<PROPP
<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
<D:prop>
<D:displayname />
<CS:getctag />
<D:resourcetype />
</D:prop>
</D:propfind>
PROPP;
if ( isset($options["PROPFIND"]) ) {
// Fetch some information about the events in that calendar
$cal->SetDepth(1);
$folder_xml = $cal->DoXMLRequest("PROPFIND", $xmlC);
print_r( $folder_xml);
}
//*******************************
//*******************************