Kind of working now, with either Lighting or Evolution.

This commit is contained in:
Andrew McMillan 2006-09-24 17:08:39 +12:00
parent a57d690167
commit 2b8b67ab0b
9 changed files with 494 additions and 60 deletions

190
dba/caldav_functions.sql Normal file
View File

@ -0,0 +1,190 @@
-- Functions for CalDAV handling
CREATE or REPLACE FUNCTION apply_month_byday( TIMESTAMP WITH TIME ZONE, TEXT ) RETURNS TIMESTAMP WITH TIME ZONE AS '
DECLARE
in_time ALIAS FOR $1;
byday ALIAS FOR $2;
weeks INT;
dow INT;
temp_txt TEXT;
dd INT;
mm INT;
yy INT;
our_dow INT;
our_answer TIMESTAMP WITH TIME ZONE;
BEGIN
dow := position(substring( byday from ''..$'') in ''SUMOTUWETHFRSA'') / 2;
temp_txt := substring(byday from ''([0-9]+)'');
weeks := temp_txt::int;
-- RAISE NOTICE ''DOW: %, Weeks: %(%s)'', dow, weeks, temp_txt;
IF substring(byday for 1) = ''-'' THEN
-- Last XX of month, or possibly second-to-last, but unlikely
mm := extract( ''month'' from in_time);
yy := extract( ''year'' from in_time);
-- Start with the last day of the month
our_answer := (yy::text || ''-'' || (mm+1)::text || ''-01'')::timestamp - ''1 day''::interval;
dd := extract( ''dow'' from our_answer);
dd := dd - dow;
IF dd < 0 THEN
dd := dd + 7;
END IF;
-- Having calculated the right day of the month, we now apply that back to in_time
-- which contains the otherwise-unobtainable timezone detail (and the time)
our_answer = our_answer - (dd::text || ''days'')::interval;
dd := extract( ''day'' from our_answer) - extract( ''day'' from in_time);
our_answer := in_time + (dd::text || ''days'')::interval;
IF weeks > 1 THEN
weeks := weeks - 1;
our_answer := our_answer - (weeks::text || ''weeks'')::interval;
END IF;
ELSE
-- Shift our date to the correct day of week..
our_dow := extract( ''dow'' from in_time);
our_dow := our_dow - dow;
dd := extract( ''day'' from in_time);
IF our_dow >= dd THEN
our_dow := our_dow - 7;
END IF;
our_answer := in_time - (our_dow::text || ''days'')::interval;
dd = extract( ''day'' from our_answer);
-- Shift the date to the correct week...
dd := weeks - ((dd+6) / 7);
IF dd != 0 THEN
our_answer := our_answer + ((dd::text || ''weeks'')::interval);
END IF;
END IF;
RETURN our_answer;
END;
' LANGUAGE 'plpgsql' IMMUTABLE STRICT;
CREATE or REPLACE FUNCTION calculate_later_timestamp( TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH TIME ZONE, TEXT ) RETURNS TIMESTAMP WITH TIME ZONE AS '
DECLARE
earliest ALIAS FOR $1;
basedate ALIAS FOR $2;
repeatrule ALIAS FOR $3;
frequency TEXT;
temp_txt TEXT;
length INT;
count INT;
byday TEXT;
bymonthday INT;
basediff INTERVAL;
past_repeats INT8;
units TEXT;
dow TEXT;
our_answer TIMESTAMP WITH TIME ZONE;
loopcount INT;
BEGIN
temp_txt := substring(repeatrule from ''UNTIL=([0-9TZ]+)(;|$)'');
IF temp_txt::timestamp with time zone < earliest THEN
RETURN NULL;
END IF;
frequency := substring(repeatrule from ''FREQ=([A-Z]+)(;|$)'');
temp_txt := substring(repeatrule from ''INTERVAL=([0-9]+)(;|$)'');
length := temp_txt::int;
basediff := earliest - basedate;
-- RAISE NOTICE ''Frequency: %, Length: %(%), Basediff: %'', frequency, length, temp_txt, basediff;
-- Calculate the number of past periods between our base date and our earliest date
IF frequency = ''WEEKLY'' OR frequency = ''DAILY'' THEN
past_repeats := extract(''epoch'' from basediff)::INT8 / 86400;
-- RAISE NOTICE ''Days: %'', past_repeats;
IF frequency = ''WEEKLY'' THEN
past_repeats := past_repeats / 7;
END IF;
ELSE
past_repeats = extract( ''years'' from basediff );
IF frequency = ''MONTHLY'' THEN
past_repeats = (past_repeats *12) + extract( ''months'' from basediff );
END IF;
END IF;
past_repeats = (past_repeats / length) + 1;
-- Check that we have not exceeded the COUNT= limit
temp_txt := substring(repeatrule from ''COUNT=([0-9]+)(;|$)'');
count := temp_txt::int;
-- RAISE NOTICE ''Periods: %, Count: %(%)'', past_repeats, count, temp_txt;
IF ( count <= past_repeats ) THEN
RETURN NULL;
END IF;
temp_txt := substring(repeatrule from ''BYSETPOS=([0-9-]+)(;|$)'');
byday := substring(repeatrule from ''BYDAY=([0-9A-Z,]+-)(;|$)'');
IF byday IS NOT NULL AND frequency = ''MONTHLY'' THEN
-- Since this could move the date around a month we go back one
-- period just to be extra sure.
past_repeats = past_repeats - 1;
IF temp_txt IS NOT NULL THEN
-- Crudely hack the BYSETPOS onto the front of BYDAY. While this
-- is not as per rfc2445, RRULE syntax is so complex and overblown
-- that nobody correctly uses comma-separated BYDAY or BYSETPOS, and
-- certainly not within a MONTHLY RRULE.
byday := temp_txt || byday;
END IF;
END IF;
past_repeats = past_repeats * length;
units := CASE
WHEN frequency = ''DAILY'' THEN ''days''
WHEN frequency = ''WEEKLY'' THEN ''weeks''
WHEN frequency = ''MONTHLY'' THEN ''months''
WHEN frequency = ''YEARLY'' THEN ''years''
END;
temp_txt := substring(repeatrule from ''BYMONTHDAY=([0-9,]+)(;|$)'');
bymonthday := temp_txt::int;
-- With all of the above calculation, this date should be close to (but less than)
-- the target, and we should only loop once or twice.
our_answer := basedate + (past_repeats::text || units)::interval;
loopcount := 1000; -- Not really needed, but stops an infinite loop if there is a bug!
LOOP
-- RAISE NOTICE ''Testing date: %'', our_answer;
IF frequency = ''WEEKLY'' THEN
-- Weekly repeats are only on specific days
-- I think this is not really right, since a WEEKLY on MO,WE,FR should
-- occur three times each week and this will only be once a week.
dow = substring( to_char( our_answer, ''DY'' ) for 2);
CONTINUE WHEN position( dow in byday ) = 0;
ELSIF frequency = ''MONTHLY'' AND byday IS NOT NULL THEN
-- This works fine, except that maybe there are multiple BYDAY
-- components. e.g. 1TU,3TU might be 1st & 3rd tuesdays.
our_answer := apply_month_byday( our_answer, byday );
ELSIF bymonthday IS NOT NULL AND frequency = ''MONTHLY'' AND bymonthday < 1 THEN
-- We do not deal with this situation at present
RAISE NOTICE ''The case of negative BYMONTHDAY is not handled yet.'';
END IF;
EXIT WHEN our_answer >= earliest;
loopcount := loopcount - 1;
IF loopcount < 0 THEN
RAISE EXCEPTION ''Could not cope with dates after % using % from %'', earliest, repeatrule, basedate;
RETURN NULL;
END IF;
-- Increment for our next time through the loop...
our_answer := our_answer + (length::text || units)::interval;
END LOOP;
RETURN our_answer;
END;
' LANGUAGE 'plpgsql' IMMUTABLE STRICT;

View File

@ -14,10 +14,10 @@ CREATE TABLE caldav_data (
caldav_type TEXT,
logged_user INT references usr(user_no),
PRIMARY KEY ( user_no, vevent_name, vevent_etag )
PRIMARY KEY ( user_no, dav_name )
);
GRANT SELECT,INSERT,UPDATE,DELETE ON vevent_data TO general;
GRANT SELECT,INSERT,UPDATE,DELETE ON caldav_data TO general;
-- Not particularly needed, perhaps, except as a way to collect
-- a bunch of valid iCalendar time zone specifications... :-)
@ -31,30 +31,84 @@ GRANT SELECT,INSERT ON time_zone TO general;
-- The parsed event. Here we have pulled those events apart somewhat.
CREATE TABLE event (
user_no INT references usr(user_no),
vevent_name TEXT,
vevent_etag TEXT,
dav_name TEXT,
dav_etag TEXT,
-- Extracted vEvent event data
uid TEXT,
dtstamp TEXT,
created TIMESTAMP,
last_modified TIMESTAMP,
dtstamp TIMESTAMP,
dtstart TIMESTAMP WITH TIME ZONE,
dtend TIMESTAMP WITH TIME ZONE,
due TIMESTAMP WITH TIME ZONE,
summary TEXT,
location TEXT,
description TEXT,
priority INT,
class TEXT,
transp TEXT,
description TEXT,
rrule TEXT,
url TEXT,
percent_complete NUMERIC(7,2),
tz_id TEXT REFERENCES time_zone( tz_id ),
-- Cascade updates / deletes from the vevent_data table
CONSTRAINT vevent_exists FOREIGN KEY ( user_no, vevent_name, vevent_etag )
REFERENCES vevent_data ( user_no, vevent_name, vevent_etag )
-- 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 event 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;
-- 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.
CREATE TABLE relationship_type (

View File

@ -7,9 +7,17 @@ dbg_error_log("delete", "DELETE method handler");
$get_path = $_SERVER['PATH_INFO'];
$etag_none_match = str_replace('"','',$_SERVER["HTTP_IF_NONE_MATCH"]);
$qry = new PgQuery( "SELECT * FROM vevent_data WHERE user_no = ? AND vevent_name = ? AND vevent_etag = ?;", $session->user_no, $get_path, $etag_none_match );
if ( $etag_none_match != '' ) {
/**
* etag_none_match is saying that we should only delete a row if it matches this etag
* (only rows not matching should exist afterwards, I guess.)
*/
$only_this_etag = " AND dav_etag = ".qpg($etag_none_match);
}
$qry = new PgQuery( "SELECT * FROM caldav_data WHERE user_no = ? AND dav_name = ? $only_this_etag;", $session->user_no, $get_path );
if ( $qry->Exec("caldav-DELETE") && $qry->rows == 1 ) {
$qry = new PgQuery( "DELETE FROM vevent_data WHERE user_no = ? AND vevent_name = ? AND vevent_etag = ?;", $session->user_no, $get_path, $etag_none_match );
$qry = new PgQuery( "DELETE FROM caldav_data WHERE user_no = ? AND dav_name = ? $only_this_etag;", $session->user_no, $get_path );
if ( $qry->Exec("caldav-DELETE") ) {
header("HTTP/1.1 200 OK");
dbg_error_log( "delete", "DELETE: User: %d, ETag: %s, Path: %s", $session->user_no, $etag_none_match, $get_path);

View File

@ -7,23 +7,23 @@ dbg_error_log("get", "GET method handler");
$get_path = $_SERVER['PATH_INFO'];
$etag_none_match = str_replace('"','',$_SERVER["HTTP_IF_NONE_MATCH"]);
$qry = new PgQuery( "SELECT * FROM vevent_data WHERE user_no = ? AND vevent_name = ? ;", $session->user_no, $get_path);
$qry = new PgQuery( "SELECT * FROM caldav_data WHERE user_no = ? AND dav_name = ? ;", $session->user_no, $get_path);
dbg_error_log("get", "%s", $qry->querystring );
if ( $qry->Exec("GET") && $qry->rows == 1 ) {
$event = $qry->Fetch();
header("HTTP/1.1 200 OK");
header("ETag: $event->vevent_etag");
header("ETag: $event->dav_etag");
header("Content-Type: text/calendar");
print $event->vevent_data;
print $event->caldav_data;
dbg_error_log( "GET", "User: %d, ETag: %s, Path: %s", $session->user_no, $event->vevent_etag, $get_path);
dbg_error_log( "GET", "User: %d, ETag: %s, Path: %s", $session->user_no, $event->dav_etag, $get_path);
}
else if ( $qry->rows != 1 ) {
header("HTTP/1.1 500 Internal Server Error");
dbg_error_log("ERROR", "Multiple rows match for User: %d, ETag: %s, Path: %s", $session->user_no, $event->vevent_etag, $get_path);
dbg_error_log("ERROR", "Multiple rows match for User: %d, ETag: %s, Path: %s", $session->user_no, $event->dav_etag, $get_path);
}
else {
header("HTTP/1.1 500 Infernal Server Error");

View File

@ -1,6 +1,8 @@
<?php
dbg_error_log("OPTIONS", "method handler");
header( "Content-type: text/plain");
header( "Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK, REPORT, ACL");
header( "DAV: 1, 2, 3, access-control, calendar-access");
// header( "Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK, REPORT, ACL");
header( "Allow: OPTIONS, GET, PUT, DELETE, REPORT");
// header( "DAV: 1, 2, 3, access-control, calendar-access");
header( "DAV: 1, calendar-access");
?>

View File

@ -28,7 +28,7 @@ if ( $etag_match == '*' || $etag_match == '' ) {
* If they didn't send an etag_match header, we need to check if the PUT object already exists
* and we are hence updating it. And we just set our etag_match to that.
*/
$qry = new PgQuery( "SELECT * FROM vevent_data WHERE user_no=? AND vevent_name=?", $session->user_no, $put_path );
$qry = new PgQuery( "SELECT * FROM caldav_data WHERE user_no=? AND dav_name=?", $session->user_no, $put_path );
$qry->Exec("PUT");
if ( $qry->rows > 1 ) {
header("HTTP/1.1 500 Infernal Server Error");
@ -37,7 +37,7 @@ if ( $etag_match == '*' || $etag_match == '' ) {
}
elseif ( $qry->rows == 1 ) {
$event = $qry->Fetch();
$etag_match = $event->vevent_etag;
$etag_match = $event->dav_etag;
}
}
@ -45,45 +45,54 @@ if ( $etag_match == '*' || $etag_match == '' ) {
/**
* If we got this far without an etag we must be inserting it.
*/
$qry = new PgQuery( "INSERT INTO vevent_data ( user_no, vevent_name, vevent_etag, vevent_data, logged_user ) VALUES( ?, ?, ?, ?, ?)",
$session->user_no, $put_path, $etag, $raw_post, $session->user_no );
$qry = new PgQuery( "INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user ) VALUES( ?, ?, ?, ?, ?, ?)",
$session->user_no, $put_path, $etag, $raw_post, $ev->type, $session->user_no );
$qry->Exec("PUT");
header("HTTP/1.1 201 Created");
header("ETag: $etag");
dbg_error_log( "PUT", "INSERT INTO vevent_data ( user_no, vevent_name, vevent_etag, vevent_data, logged_user ) VALUES( %d, '%s', '%s', '%s', %d)",
$session->user_no, $put_path, $etag, $raw_post, $session->user_no );
}
else {
$qry = new PgQuery( "UPDATE vevent_data SET vevent_data=?, vevent_etag=?, logged_user=? WHERE user_no=? AND vevent_name=? AND vevent_etag=?",
$raw_post, $etag, $session->user_no, $session->user_no, $put_path, $etag_match );
$qry = new PgQuery( "UPDATE caldav_data SET caldav_data=?, dav_etag=?, caldav_type=?, logged_user=? WHERE user_no=? AND dav_name=? AND dav_etag=?",
$raw_post, $etag, $ev->type, $session->user_no, $session->user_no, $put_path, $etag_match );
$qry->Exec("PUT");
header("HTTP/1.1 201 Replaced");
header("ETag: $etag");
}
$sql = "SET TIMEZONE TO ".qpg($ev->tz_locn).";";
if ( $ev->type == 'VEVENT' ) $table = 'event';
elseif ( $ev->type == 'VTODO' ) $table = 'todo';
$sql = ( $ev->tz_locn == '' ? '' : "SET TIMEZONE TO ".qpg($ev->tz_locn).";" );
if ( $etag_match == '*' || $etag_match == '' ) {
$sql .= <<<EOSQL
INSERT INTO event (user_no, vevent_name, vevent_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp, description, rrule, tz_id)
VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
INSERT INTO $table (user_no, dav_name, dav_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp,
description, rrule, tz_id, last_modified, url, priority, created, due, percent_complete )
VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
EOSQL;
$qry = new PgQuery( $sql, $session->user_no, $put_path, $etag, $ev->Get('uid'), $ev->Get('dtstamp'),
$ev->Get('dtstart'), $ev->Get('dtend'), $ev->Get('summary'), $ev->Get('location'),
$ev->Get('class'), $ev->Get('transp'), $ev->Get('description'), $ev->Get('rrule'), $ev->Get('tz_id') );
$ev->Get('class'), $ev->Get('transp'), $ev->Get('description'), $ev->Get('rrule'), $ev->Get('tz_id'),
$ev->Get('last-modified'), $ev->Get('url'), $ev->Get('priority'), $ev->Get('created'),
$ev->Get('due'), $ev->Get('percent-complete')
);
$qry->Exec("PUT");
}
else {
$sql = <<<EOSQL
UPDATE event SET uid=?, dtstamp=?, dtstart=?, dtend=?, summary=?, location=?, class=?, transp=?, description=?, rrule=?, tz_id=?
WHERE user_no=? AND vevent_name=? AND vevent_etag=?
UPDATE $table SET uid=?, dtstamp=?, dtstart=?, dtend=?, summary=?, location=?, class=?, transp=?, description=?, rrule=?,
tz_id=?, last_modified=?, url=?, priority=?, dav_etag=?, due=?, percent_complete=?
WHERE user_no=? AND dav_name=?
EOSQL;
$qry = new PgQuery( $sql, $ev->Get('uid'), $ev->Get('dtstamp'), $ev->Get('dtstart'), $ev->Get('dtend'), $ev->Get('summary'),
$ev->Get('location'), $ev->Get('class'), $ev->Get('transp'), $ev->Get('description'), $ev->Get('rrule'),
$ev->Get('tz_id'), $session->user_no, $put_path, $etag );
$ev->Get('tz_id'), $ev->Get('last-modified'), $ev->Get('url'), $ev->Get('priority'), $etag,
$ev->Get('due'), $ev->Get('percent-complete'),
$session->user_no, $put_path );
$qry->Exec("PUT");
}

View File

@ -28,6 +28,18 @@ foreach( $rpt_request AS $k => $v ) {
switch ( $v['tag'] ) {
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-MULTIGET':
dbg_log_array( "REPORT", "CALENDAR-MULTIGET", $v, true );
$report[$reportnum]['multiget'] = 1;
if ( $v['type'] == "open" ) {
$multiget_names = array();
}
else if ( $v['type'] == "close" ) {
$report[$reportnum]['get_names'] = $multiget_names;
unset($multiget_names);
}
break;
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-DATA':
dbg_log_array( "REPORT", "CALENDAR-DATA", $v, true );
if ( $v['type'] == "complete" ) {
@ -109,6 +121,12 @@ foreach( $rpt_request AS $k => $v ) {
}
break;
case 'DAV::HREF':
dbg_log_array( "REPORT", "DAV::HREF", $v, true );
if ( isset($report[$reportnum]['multiget']) ) {
$multiget_names[] = $v['value'];
}
default:
dbg_error_log( "REPORT", "Unhandled tag >>".$v['tag']."<<");
}
@ -172,29 +190,54 @@ REPORTHDR;
if ( isset($report[$i]['calendar-event']) ) {
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 vevent_data NATURAL JOIN event ";
$sql = "SELECT * FROM caldav_data NATURAL JOIN event WHERE caldav_type = 'VEVENT' ";
$where = "";
if ( isset( $report[$i]['start'] ) ) {
$where = "WHERE dtend >= ".qpg($report[$i]['start'])."::timestamp with time zone ";
$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'] ) ) {
if ( $where != "" ) $where .= "AND ";
$where .= "dtstart <= ".qpg($report[$i]['end'])."::timestamp with time zone ";
$where .= "AND dtstart <= ".qpg($report[$i]['end'])."::timestamp with time zone ";
}
$sql .= $where;
$qry = new PgQuery( $sql );
if ( $qry->Exec() && $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->vevent_name ) : "" );
$caldata = ( isset($report[$i]['include_data']) ? sprintf( $calendar_data_tpl, $event->vevent_data ) : "" );
printf( $response_tpl, $calhref, $event->vevent_etag, $caldata );
dbg_error_log("REPORT", "ETag >>%s<< >>http://%s:%s%s%s<<", $event->vevent_etag,
$_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['SCRIPT_NAME'], $event->vevent_name);
$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);
}
}
}
if ( isset($report[$i]['calendar-todo']) ) {
if ( isset($report[$i]['include_data']) ) dbg_error_log( "REPORT", "FIXME: Not returning full todo data" );
/**
* 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() && $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);
}
}
}
if ( isset($report[$i]['calendar-freebusy']) ) {
if ( isset($report[$i]['include_data']) ) dbg_error_log( "REPORT", "FIXME: Not returning full freebusy data" );

View File

@ -38,6 +38,12 @@ class vEvent {
*/
var $tz_locn;
/**
* The type of iCalendar data VEVENT/VTODO
* @var type string
*/
var $type;
/**#@-*/
/**
@ -49,13 +55,10 @@ class vEvent {
global $c;
// Probably a good idea to always have values for these things...
$this->properties['tz_id'] = $c->local_tzid;
$this->properties['modified'] = time();
if ( isset($c->local_tzid ) ) $this->properties['tz_id'] = $c->local_tzid;
$this->properties['dtstamp'] = date('Ymd\THis');
$this->properties['sequence'] = 1;
$this->properties['uid'] = sprintf( "%s@%s", time() * 1000 + rand(0,1000), $c->domain_name);
$this->properties['guid'] = sprintf( "%s@%s", time() * 1000 + rand(0,1000), $c->domain_name);
$this->properties['duration'] = "PT1H";
$this->properties['status'] = "TENTATIVE";
if ( !isset($args) || !is_array($args) ) return;
@ -87,7 +90,14 @@ class vEvent {
switch( $state ) {
case 0:
if ( $v == 'BEGIN:VEVENT' ) $state = $v;
if ( $v == 'BEGIN:VEVENT' ) {
$state = $v;
$this->type = 'VEVENT';
}
else if ( $v == 'BEGIN:VTODO' ) {
$state = $v;
$this->type = 'VTODO';
}
else if ( $v == 'BEGIN:VTIMEZONE' ) $state = $v;
break;
@ -95,6 +105,10 @@ class vEvent {
if ( $v == 'END:VEVENT' ) $state = 0;
break;
case 'BEGIN:VTODO':
if ( $v == 'END:VTODO' ) $state = 0;
break;
case 'BEGIN:VTIMEZONE':
if ( $v == 'END:VTIMEZONE' ) {
$state = 0;
@ -103,7 +117,7 @@ class vEvent {
break;
}
if ( $state == 'BEGIN:VEVENT' && $state != $v ) {
if ( ($state == 'BEGIN:VEVENT' || $state == 'BEGIN:VTODO') && $state != $v ) {
list( $parameter, $value ) = preg_split('/:/', $v );
if ( preg_match('/^DT[A-Z]+;TZID=/', $parameter) ) {
list( $parameter, $tz_id ) = preg_split('/;/', $parameter );
@ -133,22 +147,27 @@ class vEvent {
* them into something that PostgreSQL can understand...
*/
function DealWithTimeZones() {
$qry = new PgQuery( "SELECT tz_locn FROM time_zone WHERE tz_id = ?;", $this->properties['TZID'] );
if ( $qry->Exec('vEvent') && $qry->rows == 1 ) {
$row = $qry->Fetch();
$this->tz_locn = $row->tz_locn;
}
else {
if ( !isset($this->tz_locn) ) {
// In case there was no X-LIC-LOCATION defined, let's hope there is something in the TZID
$this->tz_locn = preg_replace('/^.*([a-z]+\/[a-z]+)$/i','$1',$this->properties['TZID'] );
if ( isset($c->save_time_zone_defs) ) {
$qry = new PgQuery( "SELECT tz_locn FROM time_zone WHERE tz_id = ?;", $this->properties['TZID'] );
if ( $qry->Exec('vEvent') && $qry->rows == 1 ) {
$row = $qry->Fetch();
$this->tz_locn = $row->tz_locn;
}
}
if ( !isset($this->tz_locn) ) {
// In case there was no X-LIC-LOCATION defined, let's hope there is something in the TZID
$this->tz_locn = preg_replace('/^.*([a-z]+\/[a-z]+)$/i','$1',$this->properties['TZID'] );
}
if ( isset($c->save_time_zone_defs) && $qry->rows != 1 ) {
$qry2 = new PgQuery( "INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES( ?, ?, ? );",
$this->properties['TZID'], $this->tz_locn, $this->properties['VTIMEZONE'] );
$qry2->Exec("vEvent");
}
}
/**
* Get the value of a property
*/
@ -156,6 +175,114 @@ class vEvent {
return $this->properties[strtoupper($key)];
}
/**
* Put the value of a property
*/
function Put( $key, $value ) {
return $this->properties[strtoupper($key)] = $value;
}
/**
* Returns a PostgreSQL Date Format string suitable for returning iCal dates
*/
function SqlDateFormat() {
return "'IYYYMMDD\"T\"HH24MISS'";
}
/**
* Returns a PostgreSQL Date Format string suitable for returning iCal durations
* - this doesn't work for negative intervals, but events should not have such!
*/
function SqlDurationFormat() {
return "'\"PT\"HH24\"H\"MI\"M\"'";
}
/*
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
BEGIN:VEVENT
UID:20060918T005755Z-21151-1000-1-7@ubu
DTSTAMP:20060918T005755Z
DTSTART;TZID=/softwarestudio.org/Olson_20011030_5/Pacific/Auckland:
20060918T153000
DTEND;TZID=/softwarestudio.org/Olson_20011030_5/Pacific/Auckland:
20060918T160000
SUMMARY:Lunch
X-EVOLUTION-CALDAV-HREF:http:
//andrew@mycaldav/caldav.php/andrew/20060918T005757Z.ics
BEGIN:VALARM
X-EVOLUTION-ALARM-UID:20060918T005755Z-21149-1000-1-12@ubu
ACTION:DISPLAY
TRIGGER;VALUE=DURATION;RELATED=START:-PT15M
DESCRIPTION:Lunch
END:VALARM
END:VEVENT
BEGIN:VTIMEZONE
TZID:/softwarestudio.org/Olson_20011030_5/Pacific/Auckland
X-LIC-LOCATION:Pacific/Auckland
BEGIN:STANDARD
TZOFFSETFROM:+1300
TZOFFSETTO:+1200
TZNAME:NZST
DTSTART:19700315T030000
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=3SU;BYMONTH=3
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+1200
TZOFFSETTO:+1300
TZNAME:NZDT
DTSTART:19701004T020000
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=10
END:DAYLIGHT
END:VTIMEZONE
END:VCALENDAR
*/
/**
* Render the vEvent object as a text string which is a single VEVENT
*/
function Render( ) {
$interesting = array( "uid", "dtstamp", "dtstart", "dtend", "duration", "summary",
"location", "description", "action", "class", "transp", "sequence");
$result = <<<EOTXT
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Catalyst.Net.NZ//NONSGML AWL Calendar//EN
VERSION:2.0
BEGIN:VEVENT
EOTXT;
foreach( $interesting AS $k => $v ) {
$v = strtoupper($v);
if ( isset($this->properties[$v]) )
$result .= sprintf("%s:%s\n", $v, $this->properties[$v]);
}
$result .= <<<EOTXT
END:VEVENT
END:VCALENDAR
EOTXT;
/*
$result = sprintf( $format,
$this->properties['UID'],
$this->properties['DTSTART'],
$this->properties['DURATION'],
$this->properties['SUMMARY'],
$this->properties['LOCATION']
);
*/
return $result;
}
}
?>
?>

View File

@ -41,5 +41,6 @@
<item url="htdocs/help.php" uploadstatus="1" />
<item url="htdocs/roles.php" uploadstatus="1" />
<item url="htdocs/relationships.php" uploadstatus="1" />
<item url="dba/caldav_functions.sql" />
</project>
</webproject>