mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-05-27 02:54:27 +00:00
Working freebusy refactored to use a single core routine.
This commit is contained in:
parent
3503f2f0fa
commit
50fccc73d8
@ -11,6 +11,7 @@ else {
|
||||
$session = new HTTPAuthSession();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Submission parameters recommended by calconnect, plus some generous alternatives
|
||||
*/
|
||||
@ -27,26 +28,52 @@ if ( !isset($fb_start) || $fb_start == '' ) $fb_start = date('Y-m-d\TH:i:s', t
|
||||
if ( (!isset($fb_period) && !isset($fb_end)) || ($fb_period == '' && $fb_end == '') )
|
||||
$fb_period = 'P44D'; // 44 days - 2 days more than recommended default
|
||||
|
||||
|
||||
/**
|
||||
* If fb_user (user, userid, user_no or email parameter) then we adjust
|
||||
* the path of the request to suit.
|
||||
*/
|
||||
if ( isset($fb_user) ) $_SERVER['PATH_INFO'] = '/'.$fb_user.'/';
|
||||
|
||||
/**
|
||||
* We also allow URLs like .../freebusy.php/user@example.com to work, so long as
|
||||
* the e-mail matches a single user whose calendar we have rights to.
|
||||
* @NOTE: It is OK for there to *be* duplicate e-mail addresses, just so long as we
|
||||
* only have read permission (or more) for only one of them.
|
||||
*/
|
||||
require_once("CalDAVRequest.php");
|
||||
$request = new CalDAVRequest(array("allow_by_email" => 1));
|
||||
$path_match = '^'.$request->path;
|
||||
if ( preg_match( '{^/(\S+@[a-z0-9][a-z0-9-]*[.][a-z0-9.-]+)/?$}i', $request->path, $matches ) ) {
|
||||
$u = getUserByEMail($matches[1]);
|
||||
$path_match = '^/'.$u->username.'/';
|
||||
}
|
||||
|
||||
if ( isset($fb_format) && $fb_format != 'text/calendar' ) {
|
||||
$request->DoResponse( 406, 'This server only supports the text/calendar format for freebusy URLs' );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We also allow URLs like .../freebusy.php/user@example.com to work, so long as
|
||||
* the e-mail matches a single user whose calendar we have rights to.
|
||||
* NOTE: It is OK for there to *be* duplicate e-mail addresses, just so long as we
|
||||
* only have read permission (or more) for only one of them.
|
||||
*/
|
||||
$request = new CalDAVRequest(array("allow_by_email" => 1));
|
||||
|
||||
if ( ! $request->HavePrivilegeTo('read-free-busy') ) $request->DoResponse( 404 );
|
||||
|
||||
require_once("freebusy-functions.php");
|
||||
|
||||
switch ( $_SERVER['REQUEST_METHOD'] ) {
|
||||
case 'GET':
|
||||
include_once("freebusy-GET.php");
|
||||
$range_start = new RepeatRuleDateTime($fb_start);
|
||||
if ( !isset($fb_end) ) {
|
||||
$range_end = clone($range_start);
|
||||
$range_end->modify($fb_period);
|
||||
}
|
||||
else {
|
||||
$range_end = new RepeatRuleDateTime($fb_end);
|
||||
}
|
||||
$freebusy = get_freebusy( $path_match, $range_start, $range_end );
|
||||
|
||||
$result = new iCalComponent();
|
||||
$result->VCalendar();
|
||||
$result->AddComponent($freebusy);
|
||||
|
||||
$request->DoResponse( 200, $result->Render(), 'text/calendar' );
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -56,4 +83,3 @@ switch ( $_SERVER['REQUEST_METHOD'] ) {
|
||||
dbg_error_log( "freebusy", "RAW: %s", str_replace("\n", "",str_replace("\r", "", $raw_post)) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -12,8 +12,8 @@ dbg_error_log("POST", "method handler");
|
||||
|
||||
require_once("XMLDocument.php");
|
||||
require_once("iCalendar.php");
|
||||
include_once("RRule.php");
|
||||
include_once('caldav-PUT-functions.php');
|
||||
include_once('freebusy-functions.php');
|
||||
|
||||
if ( ! $request->AllowedTo("CALDAV:schedule-send-freebusy")
|
||||
&& ! $request->AllowedTo("CALDAV:schedule-send-invite")
|
||||
@ -39,6 +39,13 @@ function handle_freebusy_request( $ic ) {
|
||||
|
||||
$fbq_start = $ic->GetPValue('DTSTART');
|
||||
$fbq_end = $ic->GetPValue('DTEND');
|
||||
if ( ! ( isset($fbq_start) || isset($fbq_end) ) ) {
|
||||
$request->DoResponse( 400, 'All valid freebusy requests MUST contain a DTSTART and a DTEND' );
|
||||
}
|
||||
|
||||
$range_start = new RepeatRuleDateTime($fbq_start);
|
||||
$range_end = new RepeatRuleDateTime($fbq_end);
|
||||
|
||||
$attendees = $ic->GetProperties('ATTENDEE');
|
||||
if ( preg_match( '# iCal/\d#', $_SERVER['HTTP_USER_AGENT']) ) {
|
||||
dbg_error_log( "POST", "Non-compliant iCal request. Using X-WR-ATTENDEE property" );
|
||||
@ -52,18 +59,19 @@ function handle_freebusy_request( $ic ) {
|
||||
foreach( $attendees AS $k => $attendee ) {
|
||||
$attendee_email = preg_replace( '/^mailto:/', '', $attendee->Value() );
|
||||
dbg_error_log( "POST", "Calculating free/busy for %s", $attendee_email );
|
||||
if ( ! ( isset($fbq_start) || isset($fbq_end) ) ) {
|
||||
$request->DoResponse( 400, 'All valid freebusy requests MUST contain a DTSTART and a DTEND' );
|
||||
}
|
||||
|
||||
/** @TODO: Refactor this so we only do one query here and loop through the results */
|
||||
$params = array( ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth, ':email' => $attendee_email );
|
||||
$qry = new AwlQuery('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params );
|
||||
$qry = new AwlQuery('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params );
|
||||
if ( !$qry->Exec('POST',__LINE__,__FILE__) ) $request->DoResponse( 501, 'Database error');
|
||||
if ( $qry->rows() > 1 ) {
|
||||
// Unlikely, but if we get more than one result we'll do an exact match instead.
|
||||
if ( !$qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p FROM usr JOIN principal USING(user_no) WHERE usr.email = :email', $params ) )
|
||||
if ( !$qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE usr.email = :email', $params ) )
|
||||
$request->DoResponse( 501, 'Database error');
|
||||
if ( $qry->rows() == 0 ) {
|
||||
/** Sigh... Go back to the original case-insensitive match */
|
||||
$qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params );
|
||||
}
|
||||
}
|
||||
|
||||
$response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav');
|
||||
@ -75,115 +83,19 @@ function handle_freebusy_request( $ic ) {
|
||||
$responses[] = $response;
|
||||
continue;
|
||||
}
|
||||
if ( ! $userperms = $qry->Fetch() ) $request->DoResponse( 501, 'Database error');
|
||||
if ( (privilege_to_bits('schedule-query-freebusy') & bindec($userperms->p)) == 0 ) {
|
||||
if ( ! $attendee_usr = $qry->Fetch() ) $request->DoResponse( 501, 'Database error');
|
||||
if ( (privilege_to_bits('schedule-query-freebusy') & bindec($attendee_usr->p)) == 0 ) {
|
||||
$reply->CalDAVElement($response, "request-status", "3.8;No authority" );
|
||||
$reply->CalDAVElement($response, "calendar-data" );
|
||||
$responses[] = $response;
|
||||
continue;
|
||||
}
|
||||
$attendee_path_match = '^/'.$attendee_usr->username.'/';
|
||||
$fb = get_freebusy( $attendee_path_match, $range_start, $range_end, bindec($attendee_usr->p) );
|
||||
|
||||
// If we make it here, then it seems we are allowed to see their data...
|
||||
$where = " WHERE lower(usr.email) = :email AND collection.is_calendar ";
|
||||
$params = array( ':email' => strtolower($attendee_email) );
|
||||
if ( isset( $fbq_start ) || isset( $fbq_end ) ) {
|
||||
$params[':start'] = $fbq_start;
|
||||
$params[':finish'] = $fbq_end;
|
||||
$where .= "AND rrule_event_overlaps( dtstart, dtend, rrule, :start, :finish ) ";
|
||||
}
|
||||
$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) ";
|
||||
|
||||
/**
|
||||
* Only know about PRIVATE events if you have *full* permission to the calendar
|
||||
*/
|
||||
if ( bindec($userperms->p) != privilege_to_bits('all') ) {
|
||||
$where .= "AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) ";
|
||||
}
|
||||
|
||||
$busy = array();
|
||||
$busy_tentative = array();
|
||||
/** @TODO prove this is correct */
|
||||
$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::SqlUTCFormat().") AS start, ";
|
||||
$sql .= "to_char(calendar_item.dtend at time zone 'GMT',".iCalendar::SqlUTCFormat().") AS finish ";
|
||||
$sql .= "FROM usr INNER JOIN collection USING (user_no) INNER JOIN caldav_data USING (collection_id) INNER JOIN calendar_item USING(dav_id)".$where;
|
||||
if ( isset($c->strict_result_ordering) && $c->strict_result_ordering ) $sql .= " ORDER BY dav_id";
|
||||
$qry = new AwlQuery( $sql, $params );
|
||||
if ( $qry->Exec("POST",__LINE__,__FILE__) && $qry->rows() > 0 ) {
|
||||
while( $calendar_object = $qry->Fetch() ) {
|
||||
if ( $calendar_object->transp != "TRANSPARENT" ) {
|
||||
switch ( $calendar_object->status ) {
|
||||
case "TENTATIVE":
|
||||
dbg_error_log( "POST", " 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( "POST", " FreeBusy: Not transparent, tentative or cancelled: %s, %s", $calendar_object->start, $calendar_object->finish );
|
||||
$busy[] = $calendar_object;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$i = 0;
|
||||
|
||||
$fb = new iCalComponent();
|
||||
$fb->AddProperty( 'DTSTAMP', gmdate('Ymd\THis\Z') );
|
||||
$fb->AddProperty( 'DTSTART', $fbq_start );
|
||||
$fb->AddProperty( 'DTEND', $fbq_end );
|
||||
$fb->AddProperty( 'UID', $ic->GetPValue('UID') );
|
||||
$fb->SetProperties( $ic->GetProperties('ORGANIZER'), 'ORGANIZER');
|
||||
$fb->SetType('VFREEBUSY');
|
||||
|
||||
$fb->AddProperty( $attendee );
|
||||
$fbparams = array( "FBTYPE" => "BUSY-TENTATIVE" );
|
||||
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;
|
||||
$todate = clone($date);
|
||||
$todate->AddDuration($duration);
|
||||
$fb->AddProperty("FREEBUSY", sprintf("%s/%s", $date->RenderGMT(), $todate->RenderGMT() ), $fbparams);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$finish = new iCalDate($v->finish);
|
||||
$fb->AddProperty("FREEBUSY", sprintf("%s/%s", $start->RenderGMT(), $finish->RenderGMT() ), $fbparams );
|
||||
}
|
||||
}
|
||||
|
||||
$fbparams = array( "FBTYPE" => "BUSY" );
|
||||
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;
|
||||
$todate = clone($date);
|
||||
$todate->AddDuration($duration);
|
||||
$fb->AddProperty("FREEBUSY", sprintf("%s/%s", $date->RenderGMT(), $todate->RenderGMT() ), $fbparams );
|
||||
}
|
||||
}
|
||||
else {
|
||||
$finish = new iCalDate($v->finish);
|
||||
$fb->AddProperty("FREEBUSY", sprintf("%s/%s", $start->RenderGMT(), $finish->RenderGMT() ), $fbparams );
|
||||
}
|
||||
}
|
||||
|
||||
$vcal = new iCalComponent();
|
||||
$vcal->VCalendar( array('METHOD' => 'REPLY') );
|
||||
|
||||
@ -15,9 +15,12 @@ $range_end = new RepeatRuleDateTime($fbq_end);
|
||||
|
||||
|
||||
/** We use the same code for the REPORT, the POST and the freebusy GET... */
|
||||
$freebusy = get_freebusy( $request->path.$request->DepthRegexTail(), $range_start, $range_end );
|
||||
$freebusy = get_freebusy( '^'.$request->path.$request->DepthRegexTail(), $range_start, $range_end );
|
||||
|
||||
$result = new iCalComponent();
|
||||
$result->VCalendar();
|
||||
$result->AddComponent($freebusy);
|
||||
|
||||
$request->DoResponse( 200, $freebusy, 'text/calendar' );
|
||||
$request->DoResponse( 200, $result->Render(), 'text/calendar' );
|
||||
// Won't return from that
|
||||
|
||||
|
||||
@ -6,33 +6,42 @@
|
||||
* a freebusy GET request.
|
||||
*/
|
||||
|
||||
include_once("RRule-v2.php");
|
||||
include_once('iCalendar.php');
|
||||
include_once('RRule-v2.php');
|
||||
|
||||
|
||||
function get_freebusy( $path_match, $range_start, $range_end ) {
|
||||
function get_freebusy( $path_match, $range_start, $range_end, $bin_privs = null ) {
|
||||
global $request;
|
||||
|
||||
// printf( "Path: %s\n", $path_match);
|
||||
// print_r($range_start);
|
||||
// print_r($range_end);
|
||||
|
||||
if ( !isset($bin_privs) ) $bin_privs = $request->Privileges();
|
||||
if ( !isset($range_start) || !isset($range_end) ) {
|
||||
$request->DoResponse( 400, 'All valid freebusy requests MUST contain a time-range filter' );
|
||||
}
|
||||
$params = array( ':path_match' => '^'.$path_match, ':start' => $range_start->UTC(), ':end' => $range_end->UTC() );
|
||||
$params = array( ':path_match' => $path_match, ':start' => $range_start->UTC(), ':end' => $range_end->UTC() );
|
||||
$where = ' WHERE caldav_data.dav_name ~ :path_match ';
|
||||
$where .= 'AND rrule_event_overlaps( dtstart, dtend, rrule, :start, :end) ';
|
||||
$where .= "AND caldav_data.caldav_type IN ( 'VEVENT', 'VTODO' ) ";
|
||||
$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 collection.is_calendar AND collection.schedule_transp = 'opaque' ";
|
||||
|
||||
if ( $request->Privileges() != privilege_to_bits('all') ) {
|
||||
if ( $bin_privs != privilege_to_bits('all') ) {
|
||||
$where .= "AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) ";
|
||||
}
|
||||
|
||||
|
||||
$fbtimes = 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::SqlUTCFormat().") AS start, ";
|
||||
$sql .= "to_char(calendar_item.dtend at time zone 'GMT',".iCalendar::SqlUTCFormat().") AS finish ";
|
||||
$sql .= "FROM caldav_data INNER JOIN calendar_item USING(dav_id,user_no,dav_name)".$where;
|
||||
if ( isset($c->strict_result_ordering) && $c->strict_result_ordering ) $sql .= " ORDER BY dav_id";
|
||||
$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::SqlUTCFormat().') AS start, ';
|
||||
$sql .= "to_char(calendar_item.dtend at time zone 'GMT',".iCalendar::SqlUTCFormat().') AS finish ';
|
||||
$sql .= 'FROM caldav_data INNER JOIN calendar_item USING(dav_id,user_no,dav_name,collection_id) ';
|
||||
$sql .= 'INNER JOIN collection USING(collection_id)';
|
||||
$sql .= $where;
|
||||
if ( isset($c->strict_result_ordering) && $c->strict_result_ordering ) $sql .= ' ORDER BY dav_id';
|
||||
$qry = new AwlQuery( $sql, $params );
|
||||
if ( $qry->Exec("REPORT",__LINE__,__FILE__) && $qry->rows() > 0 ) {
|
||||
while( $calendar_object = $qry->Fetch() ) {
|
||||
@ -43,13 +52,18 @@ function get_freebusy( $path_match, $range_start, $range_end ) {
|
||||
dbg_error_log( "REPORT", " FreeBusy: Not transparent, tentative or cancelled: %s, %s", $calendar_object->start, $calendar_object->finish );
|
||||
$ics = new vComponent($calendar_object->caldav_data);
|
||||
$expanded = expand_event_instances($ics, $range_start, $range_end);
|
||||
$expansion = $expanded->GetComponents( array('VEVENT','VTODO','VJOURNAL') );
|
||||
$expansion = $expanded->GetComponents( array('VEVENT'=>true,'VTODO'=>true,'VJOURNAL'=>true) );
|
||||
foreach( $expansion AS $k => $v ) {
|
||||
// echo "=====================================================\n";
|
||||
// printf( "Type: %s\n", $v->GetType());
|
||||
// echo "-----------------------------------------------------\n";
|
||||
$dtstart = $v->GetProperty('DTSTART');
|
||||
// print_r($dtstart);
|
||||
$start_date = new RepeatRuleDateTime($dtstart->Value());
|
||||
$duration = $v->GetProperty('DURATION');
|
||||
$end_date = clone($start_date);
|
||||
$end_date->modify( $duration->Value() );
|
||||
if ( $end_date < $range_start || $start_date > $range_end ) continue;
|
||||
$thisfb = $start_date->UTC() .'/'. $end_date->UTC() . $extra;
|
||||
array_push( $fbtimes, $thisfb );
|
||||
}
|
||||
@ -68,11 +82,6 @@ function get_freebusy( $path_match, $range_start, $range_end ) {
|
||||
$freebusy->AddProperty( 'FREEBUSY', $text[0], (isset($text[1]) ? array('FBTYPE' => $text[1]) : null) );
|
||||
}
|
||||
|
||||
|
||||
$result = new iCalComponent();
|
||||
$result->VCalendar();
|
||||
$result->AddComponent($freebusy);
|
||||
|
||||
return $result->Render();
|
||||
return $freebusy;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user