mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-08-09 15:06:05 +00:00
Rewrite of REPORT handling to use XML library and generally much improve
the structure of it. Major hacking on PROPFIND so that we can now work with Mulberry. Join todo & event tables into calendar_item table because having them separate was getting very silly. Remove XMLElement.php which is now in the standard libraries.
This commit is contained in:
parent
ddf01af76b
commit
0ed02b6c2e
@ -3,11 +3,13 @@
|
||||
// $c->sysabbr = 'rscds';
|
||||
// $c->admin_email = 'andrew@catalyst.net.nz';
|
||||
// $c->system_name = "Really Simple CalDAV Store";
|
||||
// $c->collections_always_exist = false;
|
||||
|
||||
$c->pg_connect[] = 'dbname=caldav port=5433 user=general';
|
||||
$c->pg_connect[] = 'dbname=caldav port=5432 user=general';
|
||||
|
||||
$c->dbg['ALL'] = 1;
|
||||
$debuggroups['querystring'] = 1;
|
||||
|
||||
$c->collections_always_exist = false;
|
||||
|
||||
?>
|
||||
@ -10,6 +10,8 @@ CREATE TABLE caldav_data (
|
||||
user_no INT references usr(user_no),
|
||||
dav_name TEXT,
|
||||
dav_etag TEXT,
|
||||
created TIMESTAMP WITH TIME ZONE,
|
||||
modified TIMESTAMP WITH TIME ZONE,
|
||||
caldav_data TEXT,
|
||||
caldav_type TEXT,
|
||||
logged_user INT references usr(user_no),
|
||||
@ -28,13 +30,13 @@ CREATE TABLE time_zone (
|
||||
);
|
||||
GRANT SELECT,INSERT ON time_zone TO general;
|
||||
|
||||
-- The parsed event. Here we have pulled those events apart somewhat.
|
||||
CREATE TABLE event (
|
||||
-- The parsed calendar item. Here we have pulled those events/todos/journals apart somewhat.
|
||||
CREATE TABLE calendar_item (
|
||||
user_no INT references usr(user_no),
|
||||
dav_name TEXT,
|
||||
dav_etag TEXT,
|
||||
|
||||
-- Extracted vEvent event data
|
||||
-- Extracted vEvent/vTodo data
|
||||
uid TEXT,
|
||||
created TIMESTAMP,
|
||||
last_modified TIMESTAMP,
|
||||
@ -59,67 +61,24 @@ CREATE TABLE event (
|
||||
MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE
|
||||
);
|
||||
|
||||
GRANT SELECT,INSERT,UPDATE,DELETE ON event TO general;
|
||||
GRANT SELECT,INSERT,UPDATE,DELETE ON calendar_item TO general;
|
||||
|
||||
-- BEGIN:VTODO
|
||||
-- CREATED:20060921T035148Z
|
||||
-- LAST-MODIFIED:20060921T035301Z
|
||||
-- DTSTAMP:20060921T035301Z
|
||||
-- UID:9a495928-276c-406b-8acd-e0883dfe68e3
|
||||
-- SUMMARY:Something to do
|
||||
-- PRIORITY:0
|
||||
-- CLASS:PUBLIC
|
||||
-- DUE;TZID=/mozilla.org/20050126_1/Antarctica/McMurdo:20060922T155149
|
||||
-- X-MOZ-LOCATIONPATH:9a495928-276c-406b-8acd-e0883dfe68e3.ics
|
||||
-- LOCATION:At work...
|
||||
-- DESCRIPTION:This needs to be done.
|
||||
-- URL:http://mcmillan.net.nz/
|
||||
-- END:VTODO
|
||||
|
||||
-- The parsed todo. Here we have pulled those todos apart somewhat.
|
||||
CREATE TABLE todo (
|
||||
user_no INT references usr(user_no),
|
||||
dav_name TEXT,
|
||||
dav_etag TEXT,
|
||||
|
||||
-- Extracted VTODO data
|
||||
uid TEXT,
|
||||
created TIMESTAMP,
|
||||
last_modified TIMESTAMP,
|
||||
dtstamp TIMESTAMP,
|
||||
dtstart TIMESTAMP WITH TIME ZONE,
|
||||
dtend TIMESTAMP WITH TIME ZONE,
|
||||
due TIMESTAMP WITH TIME ZONE,
|
||||
priority INT,
|
||||
summary TEXT,
|
||||
location TEXT,
|
||||
description TEXT,
|
||||
class TEXT,
|
||||
transp TEXT,
|
||||
rrule TEXT,
|
||||
url TEXT,
|
||||
percent_complete NUMERIC(7,2),
|
||||
tz_id TEXT REFERENCES time_zone( tz_id ),
|
||||
|
||||
-- Cascade updates / deletes from the caldav_data table
|
||||
CONSTRAINT caldav_exists FOREIGN KEY ( user_no, dav_name )
|
||||
REFERENCES caldav_data ( user_no, dav_name )
|
||||
MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE
|
||||
);
|
||||
|
||||
GRANT SELECT,INSERT,UPDATE,DELETE ON todo TO general;
|
||||
|
||||
-- Something that can look like a filesystem hierarchy where we store stuff
|
||||
CREATE TABLE calendar (
|
||||
CREATE TABLE collection (
|
||||
user_no INT references usr(user_no),
|
||||
parent_container TEXT,
|
||||
dav_name TEXT,
|
||||
dav_etag TEXT,
|
||||
dav_displayname TEXT,
|
||||
is_calendar BOOLEAN,
|
||||
created TIMESTAMP WITH TIME ZONE,
|
||||
modified TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
PRIMARY KEY ( user_no, dav_name )
|
||||
);
|
||||
|
||||
GRANT SELECT,INSERT,UPDATE,DELETE ON calendar TO general;
|
||||
GRANT SELECT,INSERT,UPDATE,DELETE ON collection TO general;
|
||||
|
||||
-- Each user can be related to each other user. This mechanism can also
|
||||
-- be used to define groups of users, since some relationships are transitive.
|
||||
|
||||
@ -1,125 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* A class to assist with construction of XML documents
|
||||
*
|
||||
* @package awl
|
||||
* @subpackage XMLElement
|
||||
* @author Andrew McMillan <andrew@catalyst.net.nz>
|
||||
* @copyright Catalyst .Net Ltd
|
||||
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
|
||||
*/
|
||||
require_once("AWLUtilities.php");
|
||||
|
||||
/**
|
||||
* A class for XML elements which may have attributes, or contain
|
||||
* other XML sub-elements
|
||||
*
|
||||
* @package awl
|
||||
*/
|
||||
class XMLElement {
|
||||
var $tagname;
|
||||
var $attributes;
|
||||
var $content;
|
||||
|
||||
/**
|
||||
* Constructor - nothing fancy as yet.
|
||||
*
|
||||
* @param string The tag name of the new element
|
||||
* @param mixed Either a string of content, or an array of sub-elements
|
||||
* @param array An array of attribute name/value pairs
|
||||
*/
|
||||
function XMLElement( $tagname, $content=false, $attributes=false ) {
|
||||
$this->tagname=$tagname;
|
||||
$this->content=$content;
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an element attribute to a value
|
||||
*
|
||||
* @param string The attribute name
|
||||
* @param string The attribute value
|
||||
*/
|
||||
function SetAttribute($k,$v) {
|
||||
if ( gettype($this->attributes) != "array" ) $this->attributes = array();
|
||||
$this->attributes[$k] = $v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whole content to a value
|
||||
*
|
||||
* @param mixed The element content, which may be text, or an array of sub-elements
|
||||
*/
|
||||
function SetContent($v) {
|
||||
$this->content = $v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sub-element
|
||||
*
|
||||
* @param object An XMLElement to be appended to the array of sub-elements
|
||||
*/
|
||||
function AddSubTag($v) {
|
||||
if ( gettype($this->content) != "array" ) $this->content = array();
|
||||
$this->content[] = $v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new sub-element
|
||||
*
|
||||
* @param string The tag name of the new element
|
||||
* @param mixed Either a string of content, or an array of sub-elements
|
||||
* @param array An array of attribute name/value pairs
|
||||
*/
|
||||
function NewElement( $tagname, $content=false, $attributes=false ) {
|
||||
if ( gettype($this->content) != "array" ) $this->content = array();
|
||||
$this->content[] = new XMLElement($tagname,$content,$attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the document tree into (nicely formatted) XML
|
||||
*
|
||||
* @param int The indenting level for the pretty formatting of the element
|
||||
*/
|
||||
function Render($indent=0) {
|
||||
$r = substr(" ",0,$indent) . '<' . $this->tagname;
|
||||
if ( gettype($this->attributes) == "array" ) {
|
||||
/**
|
||||
* Render the element attribute values
|
||||
*/
|
||||
foreach( $this->attributes AS $k => $v ) {
|
||||
$r .= sprintf( ' %s="%s"', $k, htmlspecialchars($v) );
|
||||
}
|
||||
}
|
||||
if ( (is_array($this->content) && count($this->content) > 0) || strlen($this->content) > 0 ) {
|
||||
$r .= ">";
|
||||
if ( is_array($this->content) ) {
|
||||
/**
|
||||
* Render the sub-elements with a deeper indent level
|
||||
*/
|
||||
$r .= "\n";
|
||||
foreach( $this->content AS $k => $v ) {
|
||||
if ( is_object($v) ) {
|
||||
$r .= $v->Render($indent+1);
|
||||
}
|
||||
}
|
||||
$r .= substr(" ",0,$indent);
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* Render the content, with special characters escaped
|
||||
*
|
||||
* FIXME This should switch to CDATA in some situations.
|
||||
*/
|
||||
$r .= htmlspecialchars($this->content, ENT_NOQUOTES );
|
||||
}
|
||||
$r .= '</' . $this->tagname.">\n";
|
||||
}
|
||||
else {
|
||||
$r .= "/>\n";
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@ -2,14 +2,70 @@
|
||||
|
||||
dbg_error_log("MKCALENDAR", "method handler");
|
||||
|
||||
dbg_log_array( "MKCOL", 'HEADERS', $raw_headers );
|
||||
dbg_log_array( "MKCOL", '_SERVER', $_SERVER, true );
|
||||
dbg_error_log( "MKCOL", "RAW: %s", str_replace("\n", "",str_replace("\r", "", $raw_post)) );
|
||||
|
||||
$make_path = $_SERVER['PATH_INFO'];
|
||||
|
||||
$sql = "INSERT INTO calendar ( user_no, dav_name, dav_etag, created ) VALUES( ?, ?, ?, current_timestamp );";
|
||||
$qry = new PgQuery( $sql, $session->user_no, $make_path, md5($session->user_no. $make_path) );
|
||||
$displayname = $make_path;
|
||||
$parent_container = '/';
|
||||
if ( preg_match( '#^(.*/)([^/]+)(/)?$#', $make_path, $matches ) ) {
|
||||
$parent_container = $matches[1];
|
||||
$displayname = $matches[2];
|
||||
}
|
||||
|
||||
if ( $qry->Exec("MKCALENDAR",__LINE__,__FILE__) )
|
||||
$sql = "INSERT INTO collection ( user_no, parent_container, dav_name, dav_etag, dav_displayname, is_calendar, created, modified ) VALUES( ?, ?, ?, ?, ?, TRUE, current_timestamp, current_timestamp );";
|
||||
$qry = new PgQuery( $sql, $session->user_no, $parent_container, $make_path, md5($session->user_no. $make_path), $displayname );
|
||||
|
||||
if ( $qry->Exec("MKCALENDAR",__LINE__,__FILE__) ) {
|
||||
header("HTTP/1.1 200 Created");
|
||||
else
|
||||
dbg_error_log( "MKCALENDAR", "New calendar '%s' created named '%s' for user '%d' in parent '%s'", $make_path, $displayname, $session->user_no, $parent_container);
|
||||
}
|
||||
else {
|
||||
header("HTTP/1.1 500 Infernal Server Error");
|
||||
dbg_error_log( "ERROR", " MKCOL Failed for '%s' named '%s', user '%d' in parent '%s'", $make_path, $displayname, $session->user_no, $parent_container);
|
||||
}
|
||||
|
||||
/**
|
||||
* We could also respond to the request...
|
||||
*
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<C:mkcalendar xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||
<D:set>
|
||||
<D:prop>
|
||||
<D:displayname>Lisa's Events</D:displayname>
|
||||
<C:calendar-description xml:lang="en">Calendar restricted to events.</C:calendar-description>
|
||||
<C:supported-calendar-component-set>
|
||||
<C:comp name="VEVENT"/>
|
||||
</C:supported-calendar-component-set>
|
||||
<C:calendar-timezone><![CDATA[BEGIN:VCALENDAR
|
||||
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:US-Eastern
|
||||
LAST-MODIFIED:19870101T000000Z
|
||||
BEGIN:STANDARD
|
||||
DTSTART:19671029T020000
|
||||
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||
TZOFFSETFROM:-0400
|
||||
TZOFFSETTO:-0500
|
||||
TZNAME:Eastern Standard Time (US & Canada)
|
||||
END:STANDARD
|
||||
BEGIN:DAYLIGHT
|
||||
DTSTART:19870405T020000
|
||||
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
|
||||
TZOFFSETFROM:-0500
|
||||
TZOFFSETTO:-0400
|
||||
TZNAME:Eastern Daylight Time (US & Canada)
|
||||
END:DAYLIGHT
|
||||
END:VTIMEZONE
|
||||
END:VCALENDAR
|
||||
]]></C:calendar-timezone>
|
||||
</D:prop>
|
||||
</D:set>
|
||||
</C:mkcalendar>
|
||||
|
||||
*/
|
||||
|
||||
?>
|
||||
@ -7,13 +7,13 @@ $parser = xml_parser_create_ns('UTF-8');
|
||||
xml_parser_set_option ( $parser, XML_OPTION_SKIP_WHITE, 1 );
|
||||
|
||||
function xml_start_callback( $parser, $el_name, $el_attrs ) {
|
||||
dbg_error_log( "PROPFIND", "Parsing $el_name" );
|
||||
dbg_log_array( "PROPFIND", "$el_name::attrs", $el_attrs, true );
|
||||
// dbg_error_log( "PROPFIND", "Parsing $el_name" );
|
||||
// dbg_log_array( "PROPFIND", "$el_name::attrs", $el_attrs, true );
|
||||
$attributes[$el_name] = $el_attrs;
|
||||
}
|
||||
|
||||
function xml_end_callback( $parser, $el_name ) {
|
||||
dbg_error_log( "PROPFIND", "Finished Parsing $el_name" );
|
||||
// dbg_error_log( "PROPFIND", "Finished Parsing $el_name" );
|
||||
}
|
||||
|
||||
xml_set_element_handler ( $parser, 'xml_start_callback', 'xml_end_callback' );
|
||||
@ -27,139 +27,322 @@ list( $blank, $username, $calpath ) = split( '/', $find_path, 3);
|
||||
$calpath = "/".$calpath;
|
||||
$href_list = array();
|
||||
$attribute_list = array();
|
||||
$depth = $_SERVER['HTTP_DEPTH'];
|
||||
if ( $depth == 'infinite' ) $depth = 99;
|
||||
else $depth = intval($depth);
|
||||
|
||||
// dbg_log_array("PROPFIND","_SERVER", $_SERVER, true );
|
||||
|
||||
if ( isset($debugging) ) {
|
||||
$attribute_list = array( 'GETETAG' => 1, 'GETCONTENTLENGTH' => 1, 'GETCONTENTTYPE' => 1, 'RESOURCETYPE' => 1 );
|
||||
$depth = 1;
|
||||
}
|
||||
|
||||
$unsupported = array();
|
||||
foreach( $rpt_request AS $k => $v ) {
|
||||
|
||||
switch ( $v['tag'] ) {
|
||||
|
||||
$tag = $v['tag'];
|
||||
switch ( $tag ) {
|
||||
case 'DAV::PROPFIND':
|
||||
dbg_log_array( "PROPFIND", "DAV-PROPFIND", $v, true );
|
||||
dbg_error_log( "PROPFIND", ":Request: %s -> %s", $v['type'], $tag );
|
||||
// dbg_log_array( "PROPFIND", "DAV-PROPFIND", $v, true );
|
||||
break;
|
||||
|
||||
case 'DAV::PROP':
|
||||
dbg_log_array( "PROPFIND", "DAV::PROP", $v, true );
|
||||
dbg_error_log( "PROPFIND", ":Request: %s -> %s", $v['type'], $tag );
|
||||
// dbg_log_array( "PROPFIND", "DAV::PROP", $v, true );
|
||||
break;
|
||||
|
||||
case 'DAV::GETETAG':
|
||||
case 'DAV::DISPLAYNAME':
|
||||
case 'DAV::GETCONTENTLENGTH':
|
||||
case 'DAV::GETCONTENTTYPE':
|
||||
case 'DAV::RESOURCETYPE':
|
||||
case 'DAV::CURRENT-USER-PRIVILEGE-SET':
|
||||
$attribute = substr($v['tag'],5);
|
||||
$attribute_list[$attribute] = 1;
|
||||
dbg_error_log( "PROPFIND", "Adding attribute '%s'", $attribute );
|
||||
break;
|
||||
|
||||
case 'DAV::HREF':
|
||||
dbg_log_array( "PROPFIND", "DAV::HREF", $v, true );
|
||||
// dbg_log_array( "PROPFIND", "DAV::HREF", $v, true );
|
||||
$href_list[] = $v['value'];
|
||||
break;
|
||||
|
||||
default:
|
||||
dbg_error_log( "PROPFIND", "Unhandled tag >>".$v['tag']."<<");
|
||||
if ( preg_match('/^(.*):([^:]+)$/', $tag, $matches) ) {
|
||||
$unsupported[$matches[2]] = $matches[1];
|
||||
}
|
||||
else {
|
||||
$unsupported[$tag] = "";
|
||||
}
|
||||
dbg_error_log( "PROPFIND", "Unhandled tag >>%s<<", $tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
require_once("XMLElement.php");
|
||||
|
||||
/**
|
||||
* Returns the array of privilege names converted into XMLElements
|
||||
*/
|
||||
function privileges($privilege_names) {
|
||||
$privileges = array();
|
||||
foreach( $privilege_names AS $k => $v ) {
|
||||
$privileges[] = new XMLElement("privilege", new XMLElement($v));
|
||||
}
|
||||
return $privileges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an XML sub-tree for a single collection record from the DB
|
||||
*/
|
||||
function collection_to_xml( $collection ) {
|
||||
global $attribute_list, $session, $c;
|
||||
|
||||
dbg_error_log("PROPFIND","Building XML Response for collection '%s'", $collection->dav_name );
|
||||
|
||||
$url = $_SERVER['SCRIPT_NAME'] . $collection->dav_name;
|
||||
$resourcetypes = array( new XMLElement("collection") );
|
||||
$contentlength = false;
|
||||
if ( $collection->is_calendar == 't' ) {
|
||||
$resourcetypes[] = new XMLElement("calendar", false, array("xmlns" => "urn:ietf:params:xml:ns:caldav"));
|
||||
$lqry = new PgQuery("SELECT sum(length(caldav_data)) FROM caldav_data WHERE user_no = ? AND dav_name ~ ?;", $user_no, $collection_path.'[^/]+$' );
|
||||
if ( $lqry->Exec("PROPFIND",__LINE,__FILE__) && $row = $lqry->Fetch() ) {
|
||||
$contentlength = $row->sum;
|
||||
}
|
||||
}
|
||||
$prop = new XMLElement("prop");
|
||||
if ( isset($attribute_list['GETCONTENTLENGTH']) ) {
|
||||
$prop->NewElement("getcontentlength", $contentlength );
|
||||
}
|
||||
if ( isset($attribute_list['GETCONTENTTYPE']) ) {
|
||||
// $prop->NewElement("getcontenttype", "text/calendar" );
|
||||
$prop->NewElement("getcontenttype", "httpd/unix-directory" );
|
||||
}
|
||||
if ( isset($attribute_list['RESOURCETYPE']) ) {
|
||||
$prop->NewElement("resourcetype", $resourcetypes );
|
||||
}
|
||||
if ( isset($attribute_list['DISPLAYNAME']) ) {
|
||||
$displayname = ( $collection->caldav_displayname == "" ? ucfirst(trim(str_replace("/"," ", $collection->dav_name))) : $collection->caldav_displayname );
|
||||
$prop->NewElement("displayname", $displayname );
|
||||
}
|
||||
if ( isset($attribute_list['GETETAG']) ) {
|
||||
$prop->NewElement("getetag", '"'.$collection->dav_etag.'"' );
|
||||
}
|
||||
if ( isset($attribute_list['CURRENT-USER-PRIVILEGE-SET']) ) {
|
||||
/**
|
||||
* FIXME: Fairly basic set of privileges at present.
|
||||
*/
|
||||
if ( $session->AllowedTo("Admin") && preg_match("#/.+/#", $collection->dav_name) ) {
|
||||
$privs = array("all");
|
||||
}
|
||||
else {
|
||||
$privs = array("read");
|
||||
if ( $session->user_no == $collection->user_no || $session->AllowedTo("Admin") ) {
|
||||
$privs[] = "write";
|
||||
}
|
||||
}
|
||||
$prop->NewElement("current-user-privilege-set", privileges($privs) );
|
||||
}
|
||||
$status = new XMLElement("status", "HTTP/1.1 200 OK" );
|
||||
|
||||
$propstat = new XMLElement( "propstat", array( $prop, $status) );
|
||||
$href = new XMLElement("href", $url );
|
||||
|
||||
$response = new XMLElement( "response", array($href,$propstat));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Here is the kind of thing we are going to do, returning a top-level collection
|
||||
* response, followed by a response for each calendar (or other resource) within it.
|
||||
* <?xml version='1.0' encoding='UTF-8'?>
|
||||
* <multistatus xmlns='DAV:'>
|
||||
* <response>
|
||||
* <href>/caldav.php/path/they/sent/</href>
|
||||
* <propstat>
|
||||
* <prop>
|
||||
* <getcontentlength/>
|
||||
* <getcontenttype>httpd/unix-directory</getcontenttype>
|
||||
* <resourcetype>
|
||||
* <collection/>
|
||||
* </resourcetype>
|
||||
* </prop>
|
||||
* <status>HTTP/1.1 200 OK</status>
|
||||
* </propstat>
|
||||
* </response>
|
||||
* <response>
|
||||
* <href>/caldav.php/path/they/sent/calendar</href>
|
||||
* <propstat>
|
||||
* <prop>
|
||||
* <getcontentlength/>
|
||||
* <getcontenttype>httpd/unix-directory</getcontenttype>
|
||||
* <resourcetype>
|
||||
* <collection/>
|
||||
* <calendar xmlns='urn:ietf:params:xml:ns:caldav'/>
|
||||
* </resourcetype>
|
||||
* </prop>
|
||||
* <status>HTTP/1.1 200 OK</status>
|
||||
* </propstat>
|
||||
* </response>
|
||||
* </multistatus>
|
||||
* Return XML for a single data item from the DB
|
||||
*/
|
||||
function item_to_xml( $item ) {
|
||||
global $attribute_list, $session, $c;
|
||||
|
||||
require_once("XMLElement.php");
|
||||
dbg_error_log("PROPFIND","Building XML Response for item '%s'", $item->dav_name );
|
||||
|
||||
if ( count($href_list) > 0 ) {
|
||||
// Not supported at this point...
|
||||
dbg_error_log("ERROR", " PROPFIND: Support for PROPFIND on specific URLs is not implemented");
|
||||
$url = $_SERVER['SCRIPT_NAME'] . $item->dav_name;
|
||||
$prop = new XMLElement("prop");
|
||||
if ( isset($attribute_list['GETCONTENTLENGTH']) ) {
|
||||
$contentlength = strlen($item->caldav_data);
|
||||
$prop->NewElement("getcontentlength", $contentlength );
|
||||
}
|
||||
if ( isset($attribute_list['GETCONTENTTYPE']) ) {
|
||||
$prop->NewElement("getcontenttype", "text/calendar" );
|
||||
}
|
||||
if ( isset($attribute_list['RESOURCETYPE']) ) {
|
||||
$prop->NewElement("resourcetype", new XMLElement("calendar", false, array("xmlns" => "urn:ietf:params:xml:ns:caldav")) );
|
||||
}
|
||||
if ( isset($attribute_list['DISPLAYNAME']) ) {
|
||||
$prop->NewElement("displayname");
|
||||
}
|
||||
if ( isset($attribute_list['GETETAG']) ) {
|
||||
$prop->NewElement("getetag", '"'.$item->dav_etag.'"' );
|
||||
}
|
||||
if ( isset($attribute_list['CURRENT-USER-PRIVILEGE-SET']) ) {
|
||||
/**
|
||||
* FIXME: Fairly basic set of privileges at present.
|
||||
*/
|
||||
if ( $session->AllowedTo("Admin") && preg_match("#/.+/.#", $item->dav_name) ) {
|
||||
$privs = array("all");
|
||||
}
|
||||
else {
|
||||
$privs = array("read");
|
||||
if ( $session->user_no == $item->user_no || $session->AllowedTo("Admin") ) {
|
||||
$privs[] = "write";
|
||||
}
|
||||
}
|
||||
$prop->NewElement("current-user-privilege-set", privileges($privs) );
|
||||
}
|
||||
$status = new XMLElement("status", "HTTP/1.1 200 OK" );
|
||||
|
||||
$propstat = new XMLElement( "propstat", array( $prop, $status) );
|
||||
$href = new XMLElement("href", $url );
|
||||
|
||||
$response = new XMLElement( "response", array($href,$propstat));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get XML response for items in the collection
|
||||
* If '/' is requested, a list of (FIXME: visible) users is given, otherwise
|
||||
* a list of calendars for the user which are parented by this path.
|
||||
*
|
||||
* Permissions here might well be handled through an SQL function.
|
||||
*/
|
||||
function get_collection_contents( $depth, $user_no, $collection_path ) {
|
||||
global $session;
|
||||
|
||||
dbg_error_log("PROPFIND","Getting collection contents: Depth %d, User: %d, Path: %s, IsCalendar: %s", $depth, $user_no, $collection_path, $collection->is_calendar );
|
||||
|
||||
$responses = array();
|
||||
if ( $collection->is_calendar != 't' ) {
|
||||
/**
|
||||
* Calendar collections may not contain calendar collections.
|
||||
*/
|
||||
if ( $collection_path == '/' ) {
|
||||
$sql .= "SELECT user_no, '/' || username || '/' AS dav_name, md5( '/' || username || '/') AS dav_etag, ";
|
||||
$sql .= "updated AS created, updated AS modified, fullname AS dav_displayname, FALSE AS is_calendar FROM usr";
|
||||
}
|
||||
else {
|
||||
$sql = "SELECT dav_name, dav_etag, created, modified, dav_displayname, is_calendar FROM collection WHERE parent_container=".qpg($collection_path);
|
||||
}
|
||||
$qry = new PgQuery($sql);
|
||||
|
||||
if( $qry->Exec("PROPFIND",__LINE,__FILE__) && $qry->rows > 0 ) {
|
||||
while( $collection = $qry->Fetch() ) {
|
||||
$responses[] = collection_to_xml( $collection );
|
||||
if ( $depth > 0 ) {
|
||||
$responses = array_merge( $responses, get_collection( $depth - 1, $user_no, $collection->dav_name ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbg_error_log("PROPFIND","Getting collection items: Depth %d, User: %d, Path: %s", $depth, $user_no, $collection_path );
|
||||
|
||||
$sql = "SELECT dav_name, caldav_data, dav_etag, created, modified FROM caldav_data WHERE dav_name ~ ".qpg('^'.$collection_path.'[^/]+$');
|
||||
$qry = new PgQuery($sql);
|
||||
if( $qry->Exec("PROPFIND",__LINE,__FILE__) && $qry->rows > 0 ) {
|
||||
while( $item = $qry->Fetch() ) {
|
||||
$responses[] = item_to_xml( $item );
|
||||
}
|
||||
}
|
||||
|
||||
return $responses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get XML response for a single collection. If Depth is >0 then
|
||||
* subsidiary collections will also be got up to $depth
|
||||
*/
|
||||
function get_collection( $depth, $user_no, $collection_path ) {
|
||||
global $c;
|
||||
$responses = array();
|
||||
|
||||
dbg_error_log("PROPFIND","Getting collection: Depth %d, User: %d, Path: %s", $depth, $user_no, $collection_path );
|
||||
|
||||
if ( $collection_path == '/' ) {
|
||||
$collection->dav_name = $collection_path;
|
||||
$collection->dav_etag = md5($c->system_name . $collection_path);
|
||||
$collection->is_calendar = 'f';
|
||||
$collection->dav_displayname = $c->system_name;
|
||||
$collection->created = date('Ymd"T"His');
|
||||
$responses[] = collection_to_xml( $collection );
|
||||
}
|
||||
else {
|
||||
$user_no = intval($user_no);
|
||||
if ( preg_match( '#^/[^/]+/$#', $collection_path) ) {
|
||||
$sql .= "SELECT user_no, '/' || username || '/' AS dav_name, md5( '/' || username || '/') AS dav_etag, ";
|
||||
$sql .= "updated AS created, fullname AS dav_displayname, FALSE AS is_calendar FROM usr WHERE user_no = $user_no ; ";
|
||||
}
|
||||
else {
|
||||
$sql = "SELECT dav_name, dav_etag, created, dav_displayname, is_calendar FROM collection WHERE user_no = $user_no AND dav_name = ".qpg($collection_path);
|
||||
}
|
||||
$qry = new PgQuery($sql );
|
||||
if( $qry->Exec("PROPFIND",__LINE,__FILE__) && $qry->rows > 0 && $collection = $qry->Fetch() ) {
|
||||
$responses[] = collection_to_xml( $collection );
|
||||
}
|
||||
elseif ( $c->collections_always_exist ) {
|
||||
$collection->dav_name = $collection_path;
|
||||
$collection->dav_etag = md5($collection_path);
|
||||
$collection->is_calendar = 't'; // Everything is a calendar, if it always exists!
|
||||
$collection->dav_displayname = $collection_path;
|
||||
$collection->created = date('Ymd"T"His');
|
||||
$responses[] = collection_to_xml( $collection );
|
||||
}
|
||||
}
|
||||
if ( $depth > 0 ) {
|
||||
$responses = array_merge($responses, get_collection_contents( $depth-1, $user_no, $collection_path ) );
|
||||
}
|
||||
return $responses;
|
||||
}
|
||||
|
||||
|
||||
if ( count($unsupported) > 0 ) {
|
||||
|
||||
/**
|
||||
* That's a *BAD* request!
|
||||
*/
|
||||
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
header('Content-Type: application/xml; charset="utf-8"');
|
||||
|
||||
$badprops = new XMLElement( "prop" );
|
||||
foreach( $unsupported AS $k => $v ) {
|
||||
// Not supported at this point...
|
||||
dbg_error_log("ERROR", " PROPFIND: Support for $v::$k properties is not implemented yet");
|
||||
$badprops->NewElement(strtolower($k),false,array("xmlns" => strtolower($v)));
|
||||
}
|
||||
$error = new XMLElement("error", new XMLElement( "propfind",$badprops), array("xmlns" => "DAV:") );
|
||||
// dbg_log_array( "PROPFIND", "ERRORXML", $error, true );
|
||||
|
||||
echo $error->Render(0,'<?xml version="1.0" ?>');
|
||||
exit(0);
|
||||
}
|
||||
else {
|
||||
$responses = array();
|
||||
|
||||
/**
|
||||
* Something that we can handle, at least roughly correctly.
|
||||
*/
|
||||
$url = sprintf("http://%s:%d%s%s", $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['SCRIPT_NAME'], $find_path );
|
||||
$url = $_SERVER['SCRIPT_NAME'] . $find_path ;
|
||||
$url = preg_replace( '#/$#', '', $url);
|
||||
|
||||
$sql = "SELECT * FROM calendar WHERE user_no = ? AND dav_name ~ ?;";
|
||||
if ( $calpath == '' ) {
|
||||
$sql = "SELECT user_no, '/' || username || '/' AS dav_name, md5( '/' || username || '/') AS dav_etag, updated AS created FROM usr WHERE user_no = $session->user_no UNION ".$sql;
|
||||
}
|
||||
$qry = new PgQuery($sql, $session->user_no, '^/'.$username.$calpath );
|
||||
$qry->Exec("PROPFIND",__LINE,__FILE__);
|
||||
|
||||
while( $calendar = $qry->Fetch() ) {
|
||||
$url = $_SERVER['SCRIPT_NAME'] . $calendar->dav_name;
|
||||
$resourcetypes = array( new XMLElement("collection") );
|
||||
$contentlength = false;
|
||||
if ( $calendar->dav_name != "/$username/" ) {
|
||||
$resourcetypes[] = new XMLElement("calendar", false, array("xmlns" => "urn:ietf:params:xml:ns:caldav"));
|
||||
$lqry = new PgQuery("SELECT sum(length(caldav_data)) FROM caldav_data WHERE user_no = ? AND dav_name ~ ?;", $session->user_no, '^/'.$username.$calpath.'[^/]+$' );
|
||||
if ( $lqry->Exec("PROPFIND",__LINE,__FILE__) && $row = $lqry->Fetch() ) {
|
||||
$contentlength = $row->sum;
|
||||
}
|
||||
}
|
||||
$prop = new XMLElement("prop");
|
||||
if ( isset($attribute_list['GETCONTENTLENGTH']) ) {
|
||||
$prop->NewElement("getcontentlength", $contentlength );
|
||||
}
|
||||
if ( isset($attribute_list['GETCONTENTTYPE']) ) {
|
||||
// $prop->NewElement("getcontenttype", "text/calendar" );
|
||||
$prop->NewElement("getcontenttype", "httpd/unix-directory" );
|
||||
}
|
||||
if ( isset($attribute_list['RESOURCETYPE']) ) {
|
||||
$prop->NewElement("resourcetype", $resourcetypes );
|
||||
}
|
||||
if ( isset($attribute_list['GETETAG']) ) {
|
||||
$prop->NewElement("getetag", '"'.$calendar->dav_etag.'"' );
|
||||
}
|
||||
$status = new XMLElement("status", "HTTP/1.1 200 OK" );
|
||||
|
||||
$propstat = new XMLElement( "propstat", array( $prop, $status) );
|
||||
$href = new XMLElement("href", $url );
|
||||
|
||||
$responses[] = new XMLElement( "response", array($href,$propstat));
|
||||
}
|
||||
$responses = get_collection( $depth, $session->user_no, $find_path );
|
||||
|
||||
$multistatus = new XMLElement( "multistatus", $responses, array('xmlns'=>'DAV:') );
|
||||
}
|
||||
|
||||
dbg_log_array( "PROPFIND", "XML", $multistatus, true );
|
||||
// dbg_log_array( "PROPFIND", "XML", $multistatus, true );
|
||||
$xmldoc = $multistatus->Render();
|
||||
$etag = md5($xmldoc);
|
||||
|
||||
header("HTTP/1.1 207 Multi-Status");
|
||||
header("Content-type: text/xml;charset=UTF-8");
|
||||
header("DAV: 1, 2, calendar-access, calendar-schedule");
|
||||
header("ETag: \"$etag\"");
|
||||
|
||||
echo'<?xml version="1.0" encoding="UTF-8" ?>'."\n";
|
||||
|
||||
@ -8,7 +8,7 @@ xml_parser_set_option ( $parser, XML_OPTION_SKIP_WHITE, 1 );
|
||||
|
||||
function xml_start_callback( $parser, $el_name, $el_attrs ) {
|
||||
// dbg_error_log( "REPORT", "Parsing $el_name" );
|
||||
dbg_log_array( "REPORT", "$el_name::attrs", $el_attrs, true );
|
||||
// dbg_log_array( "REPORT", "$el_name::attrs", $el_attrs, true );
|
||||
$attributes[$el_name] = $el_attrs;
|
||||
}
|
||||
|
||||
@ -22,16 +22,43 @@ $rpt_request = array();
|
||||
xml_parse_into_struct( $parser, $raw_post, $rpt_request );
|
||||
xml_parser_free($parser);
|
||||
|
||||
require_once("XMLElement.php");
|
||||
|
||||
$reportnum = -1;
|
||||
$report = array();
|
||||
foreach( $rpt_request AS $k => $v ) {
|
||||
|
||||
switch ( $v['tag'] ) {
|
||||
$fulltag = $v['tag'];
|
||||
if ( preg_match('/^(.*):([^:]+)$/', $fulltag, $matches) ) {
|
||||
$xmlns = $matches[1];
|
||||
$xmltag = $matches[2];
|
||||
}
|
||||
else {
|
||||
$xmlns = 'DAV:';
|
||||
$xmltag = $tag;
|
||||
}
|
||||
|
||||
switch ( $fulltag ) {
|
||||
|
||||
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY':
|
||||
dbg_error_log( "PROPFIND", ":Request: %s -> %s", $v['type'], $xmltag );
|
||||
if ( $v['type'] == "open" ) {
|
||||
$reportnum++;
|
||||
$report[$reportnum]['type'] = $xmltag;
|
||||
$report[$reportnum]['include_href'] = 1;
|
||||
$report[$reportnum]['include_data'] = 1;
|
||||
}
|
||||
else {
|
||||
unset($report_type);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-MULTIGET':
|
||||
dbg_log_array( "REPORT", "CALENDAR-MULTIGET", $v, true );
|
||||
dbg_error_log( "PROPFIND", ":Request: %s -> %s", $v['type'], $xmltag );
|
||||
$report[$reportnum]['multiget'] = 1;
|
||||
if ( $v['type'] == "open" ) {
|
||||
$reportnum++;
|
||||
$report[$reportnum]['type'] = $xmltag;
|
||||
$multiget_names = array();
|
||||
}
|
||||
else if ( $v['type'] == "close" ) {
|
||||
@ -40,24 +67,36 @@ foreach( $rpt_request AS $k => $v ) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-DATA':
|
||||
dbg_log_array( "REPORT", "CALENDAR-DATA", $v, true );
|
||||
if ( $v['type'] == "complete" ) {
|
||||
$report[$reportnum]['include_data'] = 1;
|
||||
case 'URN:IETF:PARAMS:XML:NS:CALDAV:FILTER':
|
||||
dbg_error_log( "PROPFIND", ":Request: %s -> %s", $v['type'], $xmltag );
|
||||
if ( $v['type'] == "open" ) {
|
||||
$filters = array();
|
||||
}
|
||||
else if ( $v['type'] == "close" ) {
|
||||
$report[$reportnum]['filters'] = $filters;
|
||||
unset($filters);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY':
|
||||
dbg_log_array( "REPORT", "CALENDAR-QUERY", $v, true );
|
||||
if ( $v['type'] == "open" ) {
|
||||
$reportnum++;
|
||||
$report_type = substr($v['tag'],30);
|
||||
$report[$reportnum]['type'] = $report_type;
|
||||
$report[$reportnum]['include_href'] = 1;
|
||||
$report[$reportnum]['include_data'] = 1;
|
||||
case 'URN:IETF:PARAMS:XML:NS:CALDAV:IS-DEFINED':
|
||||
case 'URN:IETF:PARAMS:XML:NS:CALDAV:COMP-FILTER':
|
||||
dbg_error_log( "PROPFIND", ":Request: %s -> %s", $v['type'], $xmltag );
|
||||
if ( $v['type'] == "close" ) {
|
||||
break;
|
||||
}
|
||||
if ( $v['type'] == "complete" ) {
|
||||
$filter_name = $xmltag;
|
||||
}
|
||||
else {
|
||||
unset($report_type);
|
||||
$filter_name = $v['attributes']['NAME'];
|
||||
}
|
||||
dbg_log_array( "REPORT", "COMP-FILTER", $v, true );
|
||||
if ( isset($filters) ) {
|
||||
dbg_error_log( "REPORT", "Adding filter '%s'", $filter_name );
|
||||
$filters[$filter_name] = 1;
|
||||
}
|
||||
else {
|
||||
dbg_error_log( "ERROR", "Not using COMP-FILTER '%s' outside of defined FILTER!", $filter_name );
|
||||
}
|
||||
break;
|
||||
|
||||
@ -71,23 +110,6 @@ foreach( $rpt_request AS $k => $v ) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'URN:IETF:PARAMS:XML:NS:CALDAV:COMP-FILTER':
|
||||
dbg_log_array( "REPORT", "COMP-FILTER", $v, true );
|
||||
if ( isset($v['attributes']['NAME']) && ($v['attributes']['NAME'] == 'VCALENDAR' )) {
|
||||
$report[$reportnum]['calendar'] = 1;
|
||||
}
|
||||
if ( isset($v['attributes']['NAME']) ) {
|
||||
if ( isset($report[$reportnum]['calendar']) && ($v['attributes']['NAME'] == 'VEVENT') ) {
|
||||
$report[$reportnum]['calendar-event'] = 1;
|
||||
}
|
||||
if ( isset($report[$reportnum]['calendar']) && ($v['attributes']['NAME'] == 'VTODO') ) {
|
||||
$report[$reportnum]['calendar-todo'] = 1;
|
||||
}
|
||||
if ( isset($report[$reportnum]['calendar']) && ($v['attributes']['NAME'] == 'VFREEBUSY') ) {
|
||||
$report[$reportnum]['calendar-freebusy'] = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'URN:IETF:PARAMS:XML:NS:CALDAV:FILTER':
|
||||
dbg_error_log( "REPORT", "Not using %s information which follows...", $v['tag'] );
|
||||
@ -96,7 +118,7 @@ foreach( $rpt_request AS $k => $v ) {
|
||||
|
||||
case 'DAV::PROP':
|
||||
dbg_log_array( "REPORT", "DAV::PROP", $v, true );
|
||||
if ( isset($report_type) ) {
|
||||
if ( isset($report[$reportnum]['type']) ) {
|
||||
if ( $v['type'] == "open" ) {
|
||||
$report_properties = array();
|
||||
}
|
||||
@ -113,159 +135,187 @@ foreach( $rpt_request AS $k => $v ) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-DATA':
|
||||
case 'DAV::GETETAG':
|
||||
case 'DAV::GETCONTENTLENGTH':
|
||||
case 'DAV::GETCONTENTTYPE':
|
||||
case 'DAV::RESOURCETYPE':
|
||||
if ( isset($report_properties) ) {
|
||||
$attribute = substr($v['tag'],5);
|
||||
$report_properties[$attribute] = 1;
|
||||
dbg_error_log( "REPORT", "Adding property '%s'", $xmltag );
|
||||
$report_properties[$xmltag] = 1;
|
||||
}
|
||||
else {
|
||||
dbg_error_log( "ERROR", "Not using property '%s' outside of defined report!", $xmltag );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'DAV::HREF':
|
||||
dbg_log_array( "REPORT", "DAV::HREF", $v, true );
|
||||
if ( isset($report[$reportnum]['multiget']) ) {
|
||||
$multiget_names[] = $v['value'];
|
||||
if ( $report[$reportnum]['type'] == 'CALENDAR-MULTIGET' ) {
|
||||
$value = preg_replace( "#^.*".$_SERVER['SCRIPT_NAME']."/#", "/", $v['value'] );
|
||||
$multiget_names[] = $value;
|
||||
}
|
||||
else {
|
||||
dbg_error_log( "ERROR", "Not using DAV::HREF '%s' for report type '%s'!", $v['value'], $report[$reportnum]['type'] );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
dbg_error_log( "REPORT", "Unhandled tag >>".$v['tag']."<<");
|
||||
$unsupported[$xmltag] = $xmlns;
|
||||
dbg_error_log( "REPORT", "Unhandled tag >>%s<<", $fulltag );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $unsupported_stuff ) {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
header('Content-Type: application/xml; charset="utf-8"');
|
||||
echo <<<EOXML
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:error xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||
<C:supported-filter>
|
||||
<C:prop-filter name="X-ABC-GUID"/>
|
||||
</C:supported-filter>
|
||||
</D:error>
|
||||
EOXML;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
header("HTTP/1.1 207 Multi-Status");
|
||||
header("Content-type: text/xml;charset=UTF-8");
|
||||
|
||||
/**
|
||||
* FIXME - this needs to be rewritten using XML libraries, in the same manner
|
||||
* in which the REPORT request is parsed, in fact. For the time being we will
|
||||
* attach importance to the care and feeding of Evolution, however.
|
||||
* Return XML for a single calendar (or todo) entry from the DB
|
||||
*/
|
||||
$response_tpl = <<<RESPONSETPL
|
||||
<D:response>%s
|
||||
<D:propstat>
|
||||
<D:prop>%s%s
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
function calendar_to_xml( $properties, $item ) {
|
||||
global $session, $c;
|
||||
|
||||
RESPONSETPL;
|
||||
dbg_error_log("PROPFIND","Building XML Response for item '%s'", $item->dav_name );
|
||||
|
||||
$property_tpl = <<<PROPERTYTPL
|
||||
$url = sprintf( "%s://%s:%d%s%s", 'http', $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['SCRIPT_NAME'], $item->dav_name );
|
||||
$prop = new XMLElement("prop");
|
||||
if ( isset($properties['GETCONTENTLENGTH']) ) {
|
||||
$contentlength = strlen($item->caldav_data);
|
||||
$prop->NewElement("getcontentlength", $contentlength );
|
||||
}
|
||||
if ( isset($properties['CALENDAR-DATA']) ) {
|
||||
$prop->NewElement("calendar-data", $item->caldav_data, array("xmlns" => "urn:ietf:params:xml:ns:caldav") );
|
||||
}
|
||||
if ( isset($properties['GETCONTENTTYPE']) ) {
|
||||
$prop->NewElement("getcontenttype", "text/calendar" );
|
||||
}
|
||||
if ( isset($properties['RESOURCETYPE']) ) {
|
||||
$prop->NewElement("resourcetype", new XMLElement("calendar", false, array("xmlns" => "urn:ietf:params:xml:ns:caldav")) );
|
||||
}
|
||||
if ( isset($properties['DISPLAYNAME']) ) {
|
||||
$prop->NewElement("displayname");
|
||||
}
|
||||
if ( isset($properties['GETETAG']) ) {
|
||||
$prop->NewElement("getetag", '"'.$item->dav_etag.'"' );
|
||||
}
|
||||
if ( isset($properties['CURRENT-USER-PRIVILEGE-SET']) ) {
|
||||
/**
|
||||
* FIXME: Fairly basic set of privileges at present.
|
||||
*/
|
||||
if ( $session->AllowedTo("Admin") && preg_match("#/.+/.#", $item->dav_name) ) {
|
||||
$privs = array("all");
|
||||
}
|
||||
else {
|
||||
$privs = array("read");
|
||||
if ( $session->user_no == $item->user_no || $session->AllowedTo("Admin") ) {
|
||||
$privs[] = "write";
|
||||
}
|
||||
}
|
||||
$prop->NewElement("current-user-privilege-set", privileges($privs) );
|
||||
}
|
||||
$status = new XMLElement("status", "HTTP/1.1 200 OK" );
|
||||
|
||||
<D:%s>"%s"</D:%s>
|
||||
PROPERTYTPL;
|
||||
$propstat = new XMLElement( "propstat", array( $prop, $status) );
|
||||
$href = new XMLElement("href", $url );
|
||||
|
||||
$calendar_href_tpl = <<<CALDATATPL
|
||||
$response = new XMLElement( "response", array($href,$propstat));
|
||||
|
||||
<D:href>http://%s:%d%s%s</D:href>
|
||||
CALDATATPL;
|
||||
return $response;
|
||||
}
|
||||
|
||||
$calendar_data_tpl = <<<CALDATATPL
|
||||
|
||||
<C:calendar-data>%s </C:calendar-data>
|
||||
CALDATATPL;
|
||||
|
||||
dbg_log_array("REPORT", "report", $report, true );
|
||||
if ( count($unsupported) > 0 ) {
|
||||
|
||||
echo <<<REPORTHDR
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||
/**
|
||||
* That's a *BAD* request!
|
||||
*/
|
||||
|
||||
REPORTHDR;
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
header('Content-Type: application/xml; charset="utf-8"');
|
||||
|
||||
$badprops = new XMLElement( "prop" );
|
||||
foreach( $unsupported AS $k => $v ) {
|
||||
dbg_error_log("ERROR", " REPORT: Support for $v::$k properties is not implemented yet");
|
||||
$badprops->NewElement(strtolower($k),false,array("xmlns" => strtolower($v)));
|
||||
}
|
||||
$error = new XMLElement("error", new XMLElement( "propfind",$badprops), array("xmlns" => "DAV:") );
|
||||
|
||||
echo $error->Render(0,'<?xml version="1.0" ?>');
|
||||
|
||||
exit(0);
|
||||
}
|
||||
else {
|
||||
|
||||
/**
|
||||
* Something that we can handle, at least roughly correctly.
|
||||
*/
|
||||
|
||||
$responses = array();
|
||||
|
||||
for ( $i=0; $i <= $reportnum; $i++ ) {
|
||||
dbg_error_log("REPORT", "Report[%d] Start:%s, End: %s, Events: %d, Todos: %d, Freebusy: %d",
|
||||
$i, $report[$i]['start'], $report[$i]['end'], $report[$i]['calendar-event'], $report[$i]['calendar-todo'], $report[$i]['calendar-freebusy']);
|
||||
$i, $report[$i]['start'], $report[$i]['end'], $report[$i]['filters']['VEVENT'], $report[$i]['filters']['VTODO'], $report[$i]['filters']['VFREEBUSY']);
|
||||
|
||||
if ( isset($report[$i]['calendar-event']) ) {
|
||||
/**
|
||||
* Produce VEVENT data.
|
||||
*/
|
||||
if ( isset($report[$i]['include_href']) ) dbg_error_log( "REPORT", "Returning href event data" );
|
||||
if ( isset($report[$i]['include_data']) ) dbg_error_log( "REPORT", "Returning full event data" );
|
||||
$sql = "SELECT * FROM caldav_data NATURAL JOIN event WHERE caldav_type = 'VEVENT' ";
|
||||
$where = "";
|
||||
if ( isset( $report[$i]['start'] ) ) {
|
||||
$where = "AND (dtend >= ".qpg($report[$i]['start'])."::timestamp with time zone ";
|
||||
$where .= "OR calculate_later_timestamp(".qpg($report[$i]['start'])."::timestamp with time zone,dtend,rrule) >= ".qpg($report[$i]['start'])."::timestamp with time zone) ";
|
||||
}
|
||||
if ( isset( $report[$i]['end'] ) ) {
|
||||
$where .= "AND dtstart <= ".qpg($report[$i]['end'])."::timestamp with time zone ";
|
||||
}
|
||||
$sql .= $where;
|
||||
$qry = new PgQuery( $sql );
|
||||
if ( $qry->Exec("REPORT",__LINE__,__FILE__) && $qry->rows > 0 ) {
|
||||
while( $event = $qry->Fetch() ) {
|
||||
$calhref = ( isset($report[$i]['include_href']) ? sprintf( $calendar_href_tpl, $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['SCRIPT_NAME'], $event->dav_name ) : "" );
|
||||
$caldata = ( isset($report[$i]['include_data']) ? sprintf( $calendar_data_tpl, $event->caldav_data ) : "" );
|
||||
$properties = "";
|
||||
foreach( $report[$i]['properties'] AS $k => $v ) {
|
||||
switch( $k ) {
|
||||
case 'GETETAG': $value = $event->dav_etag; break;
|
||||
case 'GETCONTENTLENGTH': $value = strlen($event->caldav_data); break;
|
||||
case 'GETCONTENTTYPE': $value = "text/calendar"; break;
|
||||
case 'RESOURCETYPE': $value = "VEVENT"; break;
|
||||
}
|
||||
$properties .= sprintf( $property_tpl, strtolower($k), $value, strtolower($k));
|
||||
}
|
||||
printf( $response_tpl, $calhref, $properties, $caldata );
|
||||
dbg_error_log("REPORT", "ETag >>%s<< >>http://%s:%s%s%s<<", $event->dav_etag,
|
||||
$_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['SCRIPT_NAME'], $event->dav_name);
|
||||
$where = "";
|
||||
switch( $report[$i]['type'] ) {
|
||||
case 'CALENDAR-QUERY':
|
||||
if ( isset( $report[$i]['start'] ) ) {
|
||||
$where = "AND (dtend >= ".qpg($report[$i]['start'])."::timestamp with time zone ";
|
||||
$where .= "OR calculate_later_timestamp(".qpg($report[$i]['start'])."::timestamp with time zone,dtend,rrule) >= ".qpg($report[$i]['start'])."::timestamp with time zone) ";
|
||||
}
|
||||
}
|
||||
if ( isset( $report[$i]['end'] ) ) {
|
||||
$where .= "AND dtstart <= ".qpg($report[$i]['end'])."::timestamp with time zone ";
|
||||
}
|
||||
break;
|
||||
|
||||
case 'CALENDAR-MULTIGET':
|
||||
$href_in = '';
|
||||
foreach( $report[$reportnum]['get_names'] AS $k => $v ) {
|
||||
dbg_error_log("REPORT", "Reporting on href '%s'", $v );
|
||||
$href_in .= ($href_in == '' ? '' : ', ');
|
||||
$href_in .= qpg($v);
|
||||
}
|
||||
if ( $href_in != "" ) {
|
||||
$where .= " AND caldav_data.dav_name IN ( $href_in ) ";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
dbg_error_log("REPORT", "Unhandled report type of '%s'", $report[$i]['type'] );
|
||||
}
|
||||
|
||||
if ( isset($report[$i]['calendar-todo']) ) {
|
||||
/**
|
||||
* Produce VTODO data.
|
||||
*/
|
||||
if ( isset($report[$i]['include_href']) ) dbg_error_log( "REPORT", "Returning href event data" );
|
||||
if ( isset($report[$i]['include_data']) ) dbg_error_log( "REPORT", "Returning full event data" );
|
||||
$sql = "SELECT * FROM caldav_data NATURAL JOIN todo WHERE caldav_type = 'VTODO' ";
|
||||
$where = "";
|
||||
if ( isset( $report[$i]['start'] ) ) {
|
||||
$where = "AND (dtend >= ".qpg($report[$i]['start'])."::timestamp with time zone ";
|
||||
$where .= "OR calculate_later_timestamp(".qpg($report[$i]['start'])."::timestamp with time zone,dtend,rrule) >= ".qpg($report[$i]['start'])."::timestamp with time zone) ";
|
||||
}
|
||||
if ( isset( $report[$i]['end'] ) ) {
|
||||
$where .= "AND dtstart <= ".qpg($report[$i]['end'])."::timestamp with time zone ";
|
||||
}
|
||||
$sql .= $where;
|
||||
$qry = new PgQuery( $sql );
|
||||
if ( $qry->Exec("REPORT",__LINE__,__FILE__) && $qry->rows > 0 ) {
|
||||
while( $event = $qry->Fetch() ) {
|
||||
$calhref = ( isset($report[$i]['include_href']) ? sprintf( $calendar_href_tpl, $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['SCRIPT_NAME'], $event->dav_name ) : "" );
|
||||
$caldata = ( isset($report[$i]['include_data']) ? sprintf( $calendar_data_tpl, $event->caldav_data ) : "" );
|
||||
printf( $response_tpl, $calhref, $event->dav_etag, $caldata );
|
||||
dbg_error_log("REPORT", "ETag >>%s<< >>http://%s:%s%s%s<<", $event->dav_etag,
|
||||
$_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['SCRIPT_NAME'], $event->dav_name);
|
||||
}
|
||||
}
|
||||
$type_filters = '';
|
||||
if ( isset($report[$i]['filters']['VEVENT']) ) {
|
||||
$type_filters .= ($type_filters == '' ? '' : ', ');
|
||||
$type_filters .= qpg('VEVENT');
|
||||
}
|
||||
if ( isset($report[$i]['calendar-freebusy']) ) {
|
||||
if ( isset($report[$i]['include_data']) ) dbg_error_log( "REPORT", "FIXME: Not returning full freebusy data" );
|
||||
if ( isset($report[$i]['filters']['VTODO']) ) {
|
||||
$type_filters .= ($type_filters == '' ? '' : ', ');
|
||||
$type_filters .= qpg('VTODO');
|
||||
}
|
||||
if ( $type_filters != '' ) {
|
||||
$where .= " AND caldav_data.caldav_type IN ( $type_filters ) ";
|
||||
}
|
||||
if ( $where != '' ) {
|
||||
$where = preg_replace( '#^\s*(AND|OR) #i', ' WHERE ', $where);
|
||||
}
|
||||
|
||||
$qry = new PgQuery( "SELECT * FROM caldav_data INNER JOIN calendar_item USING(user_no, dav_name)". $where );
|
||||
if ( $qry->Exec("REPORT",__LINE__,__FILE__) && $qry->rows > 0 ) {
|
||||
while( $calendar_object = $qry->Fetch() ) {
|
||||
$responses[] = calendar_to_xml($report[$i]['properties'], $calendar_object );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$multistatus = new XMLElement( "multistatus", $responses, array('xmlns'=>'DAV:') );
|
||||
|
||||
echo <<<EOXML
|
||||
</D:multistatus>
|
||||
EOXML;
|
||||
$xmldoc = $multistatus->Render();
|
||||
$etag = md5($xmldoc);
|
||||
|
||||
header("HTTP/1.1 207 Multi-Status");
|
||||
header("Content-type: text/xml;charset=UTF-8");
|
||||
header("ETag: \"$etag\"");
|
||||
|
||||
echo'<?xml version="1.0" encoding="UTF-8" ?>'."\n";
|
||||
echo $xmldoc;
|
||||
|
||||
?>
|
||||
37
rscds.webprj
37
rscds.webprj
@ -12,7 +12,6 @@
|
||||
<annotations/>
|
||||
<item url="inc/caldav-OPTIONS.php" uploadstatus="1" />
|
||||
<item url="inc/" uploadstatus="1" />
|
||||
<item url="inc/caldav-REPORT.php" uploadstatus="1" />
|
||||
<item url="inc/always.php" uploadstatus="1" />
|
||||
<item url="config/config.php" uploadstatus="1" />
|
||||
<item url="config/" uploadstatus="1" />
|
||||
@ -45,10 +44,44 @@
|
||||
<item url="inc/RSCDSUser.php" uploadstatus="1" />
|
||||
<item url="inc/caldav-PROPFIND.php" uploadstatus="1" />
|
||||
<item url="inc/caldav-MKCALENDAR.php" uploadstatus="1" />
|
||||
<item url="inc/XMLElement.php" uploadstatus="1" />
|
||||
<item url="htdocs/js/" uploadstatus="1" />
|
||||
<item url="htdocs/js/browse.js" uploadstatus="1" />
|
||||
<item url="htdocs/css/browse.css" uploadstatus="1" />
|
||||
<item url="htdocs/css/" uploadstatus="1" />
|
||||
<item url="inc/caldav-MKCOL.php" uploadstatus="1" />
|
||||
<item url="testing/" uploadstatus="1" />
|
||||
<item url="testing/dav_test" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/" uploadstatus="1" />
|
||||
<item url="testing/tests/" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/PROPFIND.data" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/PROPFIND.test" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/PROPFIND1.test" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/PROPFIND2.test" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/privilege.data" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/privilege.test" uploadstatus="1" />
|
||||
<item url="inc/xxxXMLElement.php" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/ticketdiscovery.test" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/ticketdiscovery.data" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/OPTIONS-top.test" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/OPTIONS-dotchandler.test" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/PROPFIND-dotchandler.test" uploadstatus="1" />
|
||||
<item url="testing/tests/evolution/OPTIONS.test" uploadstatus="1" />
|
||||
<item url="testing/tests/evolution/" uploadstatus="1" />
|
||||
<item url="testing/tests/chandler/REPORT.test" uploadstatus="1" />
|
||||
<item url="testing/tests/mulberry/" uploadstatus="1" />
|
||||
<item url="testing/tests/mulberry/PROPFIND-mulberry.data" uploadstatus="1" />
|
||||
<item url="testing/tests/mulberry/PROPFIND-mulberry.test" uploadstatus="1" />
|
||||
<item url="testing/tests/mulberry/PROPFIND-initial.test" uploadstatus="1" />
|
||||
<item url="testing/tests/mulberry/PROPFIND-initial.data" uploadstatus="1" />
|
||||
<item url="testing/tests/mulberry/REPORT-1href.data" uploadstatus="1" />
|
||||
<item url="testing/tests/evolution/REPORT.test" uploadstatus="1" />
|
||||
<item url="testing/tests/evolution/REPORT.data" uploadstatus="1" />
|
||||
<item url="testing/tests/mulberry/REPORT-1href.test" uploadstatus="1" />
|
||||
<item url="inc/xxx-caldav-REPORT.php" uploadstatus="1" />
|
||||
<item url="inc/caldav-REPORT.php" uploadstatus="1" />
|
||||
<item url="testing/tests/mulberry/PUT1.data" />
|
||||
<item url="testing/tests/mulberry/PUT2.data" />
|
||||
<item url="testing/tests/mulberry/PUT2.test" />
|
||||
<item url="testing/tests/mulberry/PUT1.test" />
|
||||
</project>
|
||||
</webproject>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user