Merge branch 'master' of github.com:DAViCal/davical into github
[davical.git] / inc / caldav-POST.php
blob8f990f9be880d310164e1e800408db7fab74d303
1 <?php
2 /**
3 * CalDAV Server - handle PUT method
5 * @package davical
6 * @subpackage caldav
7 * @author Andrew McMillan <andrew@morphoss.com>
8 * @copyright Morphoss Ltd - http://www.morphoss.com/
9 * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
11 dbg_error_log("POST", "method handler");
13 require_once("XMLDocument.php");
14 include_once('caldav-PUT-functions.php');
15 include_once('freebusy-functions.php');
16 include_once('iSchedule.php');
18 if ( ! ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['post'])) ) {
19 $fh = fopen('/var/log/davical/POST.debug','w');
20 if ( $fh ) {
21 fwrite($fh,$request->raw_post);
22 fclose($fh);
27 function handle_freebusy_request( $ic ) {
28 global $c, $session, $request, $ical;
30 $request->NeedPrivilege('CALDAV:schedule-send-freebusy');
31 $reply = new XMLDocument( array("DAV:" => "", "urn:ietf:params:xml:ns:caldav" => "C" ) );
32 $responses = array();
34 $fbq_start = $ic->GetPValue('DTSTART');
35 $fbq_end = $ic->GetPValue('DTEND');
36 if ( ! ( isset($fbq_start) || isset($fbq_end) ) ) {
37 $request->DoResponse( 400, 'All valid freebusy requests MUST contain a DTSTART and a DTEND' );
40 $range_start = new RepeatRuleDateTime($fbq_start);
41 $range_end = new RepeatRuleDateTime($fbq_end);
43 $attendees = $ic->GetProperties('ATTENDEE');
44 if ( preg_match( '# iCal/\d#', $_SERVER['HTTP_USER_AGENT']) ) {
45 dbg_error_log( "POST", "Non-compliant iCal request. Using X-WR-ATTENDEE property" );
46 $wr_attendees = $ic->GetProperties('X-WR-ATTENDEE');
47 foreach( $wr_attendees AS $k => $v ) {
48 $attendees[] = $v;
51 dbg_error_log( "POST", "Responding with free/busy for %d attendees", count($attendees) );
53 foreach( $attendees AS $k => $attendee ) {
54 $attendee_email = preg_replace( '/^mailto:/', '', $attendee->Value() );
55 dbg_error_log( "POST", "Calculating free/busy for %s", $attendee_email );
57 /** @todo Refactor this so we only do one query here and loop through the results */
58 $params = array( ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth, ':email' => $attendee_email );
59 $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 );
60 if ( !$qry->Exec('POST',__LINE__,__FILE__) ) $request->DoResponse( 501, 'Database error');
61 if ( $qry->rows() > 1 ) {
62 // Unlikely, but if we get more than one result we'll do an exact match instead.
63 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 ) )
64 $request->DoResponse( 501, 'Database error');
65 if ( $qry->rows() == 0 ) {
66 /** Sigh... Go back to the original case-insensitive match */
67 $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 );
71 $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav');
72 $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value()) );
74 if ( $qry->rows() == 0 ) {
75 $remote = new iSchedule ();
76 $answer = $remote->sendRequest ( $attendee->Value(), 'VFREEBUSY/REQUEST', $ical->Render() );
77 if ( $answer === false ) {
78 $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User" );
79 $reply->CalDAVElement($response, "calendar-data" );
80 $responses[] = $response;
81 continue;
84 foreach ( $answer as $a )
86 if ( $a === false ) {
87 $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User" );
88 $reply->CalDAVElement($response, "calendar-data" );
90 elseif ( substr( $a, 0, 1 ) >= 1 ) {
91 $reply->CalDAVElement($response, "request-status", $a );
92 $reply->CalDAVElement($response, "calendar-data" );
94 else {
95 $reply->CalDAVElement($response, "request-status", "2.0;Success" );
96 $reply->CalDAVElement($response, "calendar-data", $a );
98 $responses[] = $response;
100 continue;
102 if ( ! $attendee_usr = $qry->Fetch() ) $request->DoResponse( 501, 'Database error');
103 if ( (privilege_to_bits('schedule-query-freebusy') & bindec($attendee_usr->p)) == 0 ) {
104 $reply->CalDAVElement($response, "request-status", "3.8;No authority" );
105 $reply->CalDAVElement($response, "calendar-data" );
106 $responses[] = $response;
107 continue;
109 $attendee_path_match = '^/'.$attendee_usr->username.'/';
110 $fb = get_freebusy( $attendee_path_match, $range_start, $range_end, bindec($attendee_usr->p) );
112 $fb->AddProperty( 'UID', $ic->GetPValue('UID') );
113 $fb->SetProperties( $ic->GetProperties('ORGANIZER'), 'ORGANIZER');
114 $fb->AddProperty( $attendee );
116 $vcal = new vCalendar( array('METHOD' => 'REPLY') );
117 $vcal->AddComponent( $fb );
119 $response = $reply->NewXMLElement( "response", false, false, 'urn:ietf:params:xml:ns:caldav' );
120 $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value()) );
121 $reply->CalDAVElement($response, "request-status", "2.0;Success" ); // Cargo-cult setting
122 $reply->CalDAVElement($response, "calendar-data", $vcal->Render() );
123 $responses[] = $response;
126 $response = $reply->NewXMLElement( "schedule-response", $responses, $reply->GetXmlNsArray(), 'urn:ietf:params:xml:ns:caldav' );
127 $request->XMLResponse( 200, $response );
131 function handle_cancel_request( $ic ) {
132 global $c, $session, $request;
134 $request->NeedPrivilege('CALDAV:schedule-send-reply');
136 $reply = new XMLDocument( array("DAV:" => "", "urn:ietf:params:xml:ns:caldav" => "C" ) );
138 $response = $reply->NewXMLElement( "response", false, false, 'urn:ietf:params:xml:ns:caldav' );
139 $reply->CalDAVElement($response, "request-status", "2.0;Success" ); // Cargo-cult setting
140 $response = $reply->NewXMLElement( "schedule-response", $response, $reply->GetXmlNsArray() );
141 $request->XMLResponse( 200, $response );
145 $ical = new vCalendar( $request->raw_post );
146 $method = $ical->GetPValue('METHOD');
148 $resources = $ical->GetComponents('VTIMEZONE',false);
149 $first = $resources[0];
150 switch ( $method ) {
151 case 'REQUEST':
152 dbg_error_log('POST', 'Handling iTIP "REQUEST" method with "%s" component.', $method, $first->GetType() );
153 if ( $first->GetType() == 'VFREEBUSY' )
154 handle_freebusy_request( $first );
155 elseif ( $first->GetType() == 'VEVENT' ) {
156 $request->NeedPrivilege('CALDAV:schedule-send-invite');
157 handle_schedule_request( $ical );
159 else {
160 dbg_error_log('POST', 'Ignoring iTIP "REQUEST" with "%s" component.', $first->GetType() );
162 break;
163 case 'REPLY':
164 dbg_error_log('POST', 'Handling iTIP "REPLY" with "%s" component.', $first->GetType() );
165 $request->NeedPrivilege('CALDAV:schedule-send-reply');
166 handle_schedule_reply ( $ical );
167 break;
169 case 'CANCEL':
170 dbg_error_log("POST", "Handling iTIP 'CANCEL' method.", $method );
171 handle_cancel_request( $first );
172 break;
174 default:
175 dbg_error_log("POST", "Unhandled '%s' method in request.", $method );