From e3ee3970088e955228826f3534f4a9f076df2994 Mon Sep 17 00:00:00 2001 From: Andrew McMillan Date: Mon, 4 Jun 2007 23:23:46 +1200 Subject: [PATCH] Start refactoring caldav-REPORT. --- inc/caldav-REPORT-freebusy.php | 95 +++++++++++++++++ inc/caldav-REPORT.php | 186 ++++++++++++++------------------- 2 files changed, 176 insertions(+), 105 deletions(-) create mode 100644 inc/caldav-REPORT-freebusy.php diff --git a/inc/caldav-REPORT-freebusy.php b/inc/caldav-REPORT-freebusy.php new file mode 100644 index 00000000..5edfd2b5 --- /dev/null +++ b/inc/caldav-REPORT-freebusy.php @@ -0,0 +1,95 @@ +GetContent('URN:IETF:PARAMS:XML:NS:CALDAV:FREE-BUSY-QUERY'); +$fbq_start = $fbq_content[0]->GetAttribute('START'); +$fbq_end = $fbq_content[0]->GetAttribute('END'); + +if ( ! ( isset($fbq_start) || isset($fbq_end) ) ) { + $request->DoResponse( 400, 'All valid freebusy requests MUST contain a time-range filter' ); +} +$where = " WHERE caldav_data.dav_name ~ ? "; +if ( isset( $fbq_start ) ) { + $where .= "AND (dtend >= ".qpg($fbq_start)."::timestamp with time zone "; + $where .= "OR calculate_later_timestamp(".qpg($fbq_start)."::timestamp with time zone,dtend,rrule) >= ".qpg($fbq_start)."::timestamp with time zone) "; +} +if ( isset( $fbq_end ) ) { + $where .= "AND dtstart <= ".qpg($fbq_end)."::timestamp with time zone "; +} +$where .= "AND caldav_data.caldav_type IN ( 'VEVENT', 'VFREEBUSY' ) "; +$where .= "AND (calendar_item.transp != 'TRANSPARENT' OR calendar_item.transp IS NULL) "; +$where .= "AND (calendar_item.status != 'CANCELLED' OR calendar_item.status IS NULL) "; +$where .= "AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL OR get_permissions($session->user_no,caldav_data.user_no) ~ 'A') "; // Must have 'all' permissions to see confidential items + +$busy = array(); +$busy_tentative = array(); +$sql = "SELECT caldav_data.caldav_data, calendar_item.rrule, calendar_item.transp, calendar_item.status, "; +$sql .= "to_char(calendar_item.dtstart at time zone 'GMT',".iCalendar::SqlDateFormat().") AS start, "; +$sql .= "to_char(calendar_item.dtend at time zone 'GMT',".iCalendar::SqlDateFormat().") AS finish "; +$sql .= "FROM caldav_data INNER JOIN calendar_item USING(user_no, dav_name)".$where." ORDER BY dtstart, dtend"; +$qry = new PgQuery( $sql, "^".$request->path.$request->DepthRegexTail() ); +if ( $qry->Exec("REPORT",__LINE__,__FILE__) && $qry->rows > 0 ) { + while( $calendar_object = $qry->Fetch() ) { + if ( $calendar_object->transp != "TRANSPARENT" ) { + switch ( $calendar_object->status ) { + case "TENTATIVE": + dbg_error_log( "REPORT", " FreeBusy: tentative appointment: %s, %s", $calendar_object->start, $calendar_object->finish ); + $busy_tentative[] = $calendar_object; + break; + + case "CANCELLED": + // Cancelled events are ignored + break; + + default: + dbg_error_log( "REPORT", " FreeBusy: Not transparent, tentative or cancelled: %s, %s", $calendar_object->start, $calendar_object->finish ); + $busy[] = $calendar_object; + break; + } + } + } +} +$freebusy = iCalendar::iCalHeader(); +$freebusy .= sprintf("BEGIN:VFREEBUSY\nDTSTAMP:%s\nDTSTART:%s\nDTEND:%s\n", date('Ymd\THis\Z'), $fbq_start, $fbq_end); + +foreach( $busy_tentative AS $k => $v ) { + $start = new iCalDate($v->start); + $duration = $start->DateDifference($v->finish); + if ( $v->rrule != "" ) { + $rrule = new RRule( $start, $v->rrule ); + while ( $date = $rrule->GetNext() ) { + if ( ! $date->GreaterThan($fbq_start) ) continue; + if ( $date->GreaterThan($fbq_end) ) break; + $freebusy .= sprintf("FREEBUSY;FBTYPE=BUSY-TENTATIVE:%s/%s\n", $date->Render('Ymd\THis'), $duration ); + } + } + else { + $freebusy .= sprintf("FREEBUSY;FBTYPE=BUSY-TENTATIVE:%s/%s\n", $start->Render('Ymd\THis'), $duration ); + } +} + +foreach( $busy AS $k => $v ) { + $start = new iCalDate($v->start); + $duration = $start->DateDifference($v->finish); + if ( $v->rrule != "" ) { + $rrule = new RRule( $start, $v->rrule ); + while ( $date = $rrule->GetNext() ) { + if ( ! $date->GreaterThan($fbq_start) ) continue; + if ( $date->GreaterThan($fbq_end) ) break; + $freebusy .= sprintf("FREEBUSY:%s/%s\n", $date->Render('Ymd\THis'), $duration ); + } + } + else { + $freebusy .= sprintf("FREEBUSY:%s/%s\n", $start->Render('Ymd\THis'), $duration ); + } +} + +$freebusy .= "END:VFREEBUSY\n"; +$freebusy .= iCalendar::iCalFooter(); +$request->DoResponse( 200, $freebusy, 'text/calendar' ); +// Won't return from that + diff --git a/inc/caldav-REPORT.php b/inc/caldav-REPORT.php index d8a634b3..cc6fc9ab 100644 --- a/inc/caldav-REPORT.php +++ b/inc/caldav-REPORT.php @@ -21,6 +21,9 @@ if ( ! ($request->AllowedTo('read') || $request->AllowedTo('freebusy')) ) { $request->DoResponse( 404 ); } +if ( !isset($request->xml_tags) ) { + $request->DoResponse( 403, "REPORT body contains no XML data!" ); +} require_once("XMLElement.php"); require_once("iCalendar.php"); @@ -32,6 +35,30 @@ $free_busy_query = false; $reportnum = -1; $report = array(); +if ( isset($prop_filter) ) unset($prop_filter); + +$position = 0; +$xmltree = BuildXMLTree( $request->xml_tags, $position); +if ( $xmltree->GetTag() == "URN:IETF:PARAMS:XML:NS:CALDAV:FREE-BUSY-QUERY" ) { + include("caldav-REPORT-freebusy.php"); + exit; // Not that the above include should return anyway +} + +// Must have read privilege for all other reports +if ( ! ($request->AllowedTo('read') ) ) $request->DoResponse( 403, translate("You may not access that calendar") ); + +if ( $xmltree->GetTag() == "URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY" ) { + $calquery = $xmltree->GetPath("/URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY/*"); +// include("caldav-REPORT-calendar.php"); +} +elseif ( $xmltree->GetTag() == "URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-MULTIGET" ) { + $multiget = $xmltree->GetPath("/URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-MULTIGET/*"); +// include("caldav-REPORT-multiget.php"); +} +else { + $request->DoResponse( 501, "XML is not a supported REPORT query document" ); +} + foreach( $request->xml_tags AS $k => $v ) { $fulltag = $v['tag']; @@ -46,17 +73,6 @@ foreach( $request->xml_tags AS $k => $v ) { switch ( $fulltag ) { - case 'URN:IETF:PARAMS:XML:NS:CALDAV:FREE-BUSY-QUERY': - dbg_error_log( "REPORT", ":Request: %s -> %s", $v['type'], $xmltag ); - $free_busy_query = true; - if ( $v['type'] == "open" ) { - $reportnum++; - $report[$reportnum]['type'] = $xmltag; - $report[$reportnum]['include_href'] = 1; - $report[$reportnum]['include_data'] = 1; - } - break; - case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY': dbg_error_log( "REPORT", ":Request: %s -> %s", $v['type'], $xmltag ); if ( $v['type'] == "open" ) { @@ -122,12 +138,35 @@ foreach( $request->xml_tags AS $k => $v ) { } break; - + case 'URN:IETF:PARAMS:XML:NS:CALDAV:FILTER': dbg_error_log( "REPORT", "Not using %s information which follows...", $v['tag'] ); dbg_log_array( "REPORT", "FILTER", $v, true ); break; + + case 'URN:IETF:PARAMS:XML:NS:CALDAV:PROP-FILTER': + dbg_log_array( "REPORT", "PROP-FILTER", $v, true ); + if ( $v['type'] == "open" ) { + $prop_filter = array( "name" => $v['attributes']['NAME'] ); + } + elseif ( $v['type'] == "close" ) { + $report[$reportnum]['propfilter'] = $prop_filter; + unset($prop_filter); + } + break; + + case 'URN:IETF:PARAMS:XML:NS:CALDAV:TEXT-MATCH': + dbg_log_array( "REPORT", "TEXT-MATCH", $v, true ); + $prop_filter["text-match"] = $v['value']; + break; + + case 'URN:IETF:PARAMS:XML:NS:CALDAV:IS-NOT-DEFINED': + dbg_log_array( "REPORT", "TEXT-MATCH", $v, true ); + $prop_filter["is-not-defined"] = 1; + break; + + case 'DAV::PROP': dbg_log_array( "REPORT", "DAV::PROP", $v, true ); if ( isset($report[$reportnum]['type']) ) { @@ -180,6 +219,33 @@ foreach( $request->xml_tags AS $k => $v ) { /** * Return XML for a single calendar (or todo) entry from the DB +* +* @param array $filter The definition of the prop-filter +* @param string $item The SQL calendar row for this calendar +* +* @return boolean True if the check succeeded, false otherwise. +*/ +function check_prop_filter( $filter, $item ) { + global $session, $c, $request; + + dbg_error_log("REPORT","Checking property filter for item '%s'", $item->dav_name ); + $ical = new iCalendar( array( "icalendar" => $item->caldav_data) ); + $property = $ical->Get($filter["name"]); + if ( $property == "" && isset($filter["is-not-defined"]) ) return true; + + foreach( $filter AS $k => $v ) { + if ( $k == 'name' || $k == 'is-not-defined' ) continue; + } +} + + +/** +* Return XML for a single calendar (or todo) entry from the DB +* +* @param array $properties The properties for this calendar +* @param string $item The calendar data for this calendar +* +* @return string An XML document which is the response for the calendar */ function calendar_to_xml( $properties, $item ) { global $session, $c, $request; @@ -242,98 +308,6 @@ function calendar_to_xml( $properties, $item ) { return $response; } -if ( $free_busy_query ) { - include_once("iCalendar.php"); - include_once("RRule.php"); - - if ( ! ( isset($report[0]['start']) || isset($report[0]['end']) ) ) { - $request->DoResponse( 400, 'All valid freebusy requests MUST contain a time-range filter' ); - } - $where = " WHERE caldav_data.dav_name ~ ? "; - if ( isset( $report[0]['start'] ) ) { - $where .= "AND (dtend >= ".qpg($report[0]['start'])."::timestamp with time zone "; - $where .= "OR calculate_later_timestamp(".qpg($report[0]['start'])."::timestamp with time zone,dtend,rrule) >= ".qpg($report[0]['start'])."::timestamp with time zone) "; - } - if ( isset( $report[0]['end'] ) ) { - $where .= "AND dtstart <= ".qpg($report[0]['end'])."::timestamp with time zone "; - } - $where .= "AND caldav_data.caldav_type IN ( 'VEVENT', 'VFREEBUSY' ) "; - $where .= "AND (calendar_item.transp != 'TRANSPARENT' OR calendar_item.transp IS NULL) "; - $where .= "AND (calendar_item.status != 'CANCELLED' OR calendar_item.status IS NULL) "; - $where .= "AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL OR get_permissions($session->user_no,caldav_data.user_no) ~ 'A') "; // Must have 'all' permissions to see confidential items - - $busy = array(); - $busy_tentative = array(); - $sql = "SELECT caldav_data.caldav_data, calendar_item.rrule, calendar_item.transp, calendar_item.status, "; - $sql .= "to_char(calendar_item.dtstart at time zone 'GMT',".iCalendar::SqlDateFormat().") AS start, "; - $sql .= "to_char(calendar_item.dtend at time zone 'GMT',".iCalendar::SqlDateFormat().") AS finish "; - $sql .= "FROM caldav_data INNER JOIN calendar_item USING(user_no, dav_name)".$where." ORDER BY dtstart, dtend"; - $qry = new PgQuery( $sql, "^".$request->path.$request->DepthRegexTail() ); - if ( $qry->Exec("REPORT",__LINE__,__FILE__) && $qry->rows > 0 ) { - while( $calendar_object = $qry->Fetch() ) { - if ( $calendar_object->transp != "TRANSPARENT" ) { - switch ( $calendar_object->status ) { - case "TENTATIVE": - dbg_error_log( "REPORT", " FreeBusy: tentative appointment: %s, %s", $calendar_object->start, $calendar_object->finish ); - $busy_tentative[] = $calendar_object; - break; - - case "CANCELLED": - // Cancelled events are ignored - break; - - default: - dbg_error_log( "REPORT", " FreeBusy: Not transparent, tentative or cancelled: %s, %s", $calendar_object->start, $calendar_object->finish ); - $busy[] = $calendar_object; - break; - } - } - } - } - $freebusy = iCalendar::iCalHeader(); - $freebusy .= sprintf("BEGIN:VFREEBUSY\nDTSTAMP:%s\nDTSTART:%s\nDTEND:%s\n", date('Ymd\THis\Z'), $report[0]['start'], $report[0]['end']); - - foreach( $busy_tentative AS $k => $v ) { - $start = new iCalDate($v->start); - $duration = $start->DateDifference($v->finish); - if ( $v->rrule != "" ) { - $rrule = new RRule( $start, $v->rrule ); - while ( $date = $rrule->GetNext() ) { - if ( ! $date->GreaterThan($report[0]['start']) ) continue; - if ( $date->GreaterThan($report[0]['end']) ) break; - $freebusy .= sprintf("FREEBUSY;FBTYPE=BUSY-TENTATIVE:%s/%s\n", $date->Render('Ymd\THis'), $duration ); - } - } - else { - $freebusy .= sprintf("FREEBUSY;FBTYPE=BUSY-TENTATIVE:%s/%s\n", $start->Render('Ymd\THis'), $duration ); - } - } - - foreach( $busy AS $k => $v ) { - $start = new iCalDate($v->start); - $duration = $start->DateDifference($v->finish); - if ( $v->rrule != "" ) { - $rrule = new RRule( $start, $v->rrule ); - while ( $date = $rrule->GetNext() ) { - if ( ! $date->GreaterThan($report[0]['start']) ) continue; - if ( $date->GreaterThan($report[0]['end']) ) break; - $freebusy .= sprintf("FREEBUSY:%s/%s\n", $date->Render('Ymd\THis'), $duration ); - } - } - else { - $freebusy .= sprintf("FREEBUSY:%s/%s\n", $start->Render('Ymd\THis'), $duration ); - } - } - - $freebusy .= "END:VFREEBUSY\n"; - $freebusy .= iCalendar::iCalFooter(); - $request->DoResponse( 200, $freebusy, 'text/calendar' ); - // Won't return from that -} -else { - // Must have read privilege for other reports - if ( ! ($request->AllowedTo('read') ) ) $request->DoResponse( 403, translate("You may not access that calendar") ); -} $request->UnsupportedRequest($unsupported); // Won't return if there was unsupported stuff. @@ -395,7 +369,9 @@ for ( $i=0; $i <= $reportnum; $i++ ) { $qry = new PgQuery( "SELECT * , get_permissions($session->user_no,caldav_data.user_no) as permissions 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 ); + if ( !isset($report[$reportnum]['propfilter']) || check_prop_filter( $report[$reportnum]['propfilter'], $calendar_object ) ) { + $responses[] = calendar_to_xml( $report[$i]['properties'], $calendar_object ); + } } } }