mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-05-30 03:24:47 +00:00
Kind of working now, with either Lighting or Evolution.
This commit is contained in:
parent
a57d690167
commit
2b8b67ab0b
190
dba/caldav_functions.sql
Normal file
190
dba/caldav_functions.sql
Normal 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;
|
||||
@ -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 (
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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");
|
||||
?>
|
||||
@ -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");
|
||||
}
|
||||
|
||||
|
||||
@ -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" );
|
||||
|
||||
161
inc/vEvent.php
161
inc/vEvent.php
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
?>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user