diff --git a/htdocs/caldav.php b/htdocs/caldav.php index ba7fc896..da9fbc79 100644 --- a/htdocs/caldav.php +++ b/htdocs/caldav.php @@ -119,6 +119,7 @@ try { include('caldav-POST.php'); break; } + // fall through if POST add member case 'PUT': switch( $request->content_type ) { case 'text/calendar': diff --git a/inc/caldav-PUT-functions.php b/inc/caldav-PUT-functions.php index 5a2064c3..f2ad437e 100644 --- a/inc/caldav-PUT-functions.php +++ b/inc/caldav-PUT-functions.php @@ -166,6 +166,7 @@ function public_events_only( $user_no, $dav_name ) { return false; } + /** * Get a TZID string from this VEVENT/VTODO/... component if we can * @param vComponent $comp @@ -180,6 +181,7 @@ function GetTZID( vComponent $comp ) { return $p->GetParameterValue('TZID'); } + /** * Deliver scheduling requests to attendees * @param vComponent $ical the VCALENDAR to deliver @@ -268,6 +270,7 @@ function handle_schedule_request( $ical ) { $request->DoResponse( 201, 'Created' ); } + /** * Deliver scheduling replies to organizer and other attendees * @param vComponent $ical the VCALENDAR to deliver @@ -653,7 +656,7 @@ function do_scheduling_requests( vCalendar $resource, $create, $old_data = null */ function import_collection( $import_content, $user_no, $path, $caldav_context, $appending = false ) { global $c; - + if ( ! ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['put'])) ) { $fh = fopen('/var/log/davical/PUT-2.debug','w'); if ( $fh ) { @@ -667,7 +670,7 @@ function import_collection( $import_content, $user_no, $path, $caldav_context, $ import_addressbook_collection( $import_content, $user_no, $path, $caldav_context, $appending ); elseif ( $matches[1] == 'VCALENDAR' ) import_calendar_collection( $import_content, $user_no, $path, $caldav_context, $appending ); - + // Uncache anything to do with the collection $cache = getCacheInstance(); $cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $path); @@ -675,9 +678,10 @@ function import_collection( $import_content, $user_no, $path, $caldav_context, $ } else { dbg_error_log('PUT', 'Can only import files which are VCARD or VCALENDAR'); - } + } } + /** * This function will import a whole addressbook * @param string $vcard_content the vcf file to import @@ -700,7 +704,7 @@ function import_addressbook_collection( $vcard_content, $user_no, $path, $caldav rollback_on_error( $caldav_context, $user_no, $path, sprintf('Error: Collection does not exist at "%s" for user %d', $path, $user_no )); } $collection = $qry->Fetch(); - + if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) $qry->Begin(); $base_params = array( ':collection_id' => $collection->collection_id, @@ -711,17 +715,17 @@ function import_addressbook_collection( $vcard_content, $user_no, $path, $caldav if ( !$qry->QDo('DELETE FROM caldav_data WHERE collection_id = :collection_id', $base_params) ) rollback_on_error( $caldav_context, $user_no, $collection->collection_id, 'Database error on DELETE of existing rows' ); } - + $dav_data_insert = <<GetComponents(); foreach( $resources AS $k => $resource ) { if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin(); - + $vcard = new vCard( $resource->Render() ); $uid = $vcard->GetPValue('UID'); @@ -743,7 +747,7 @@ EOSQL; } $rendered_card = $vcard->Render(); - + $dav_data_params = $base_params; $dav_data_params[':user_no'] = $user_no; // We don't allow any of &?\/@%+: in the UID to appear in the path, but anything else is fair game. @@ -752,27 +756,28 @@ EOSQL; $dav_data_params[':dav_data'] = $rendered_card; $dav_data_params[':modified'] = $last_modified; $dav_data_params[':created'] = $created; - + if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin(); - + if ( !$qry->QDo($dav_data_insert,$dav_data_params) ) rollback_on_error( $caldav_context, $user_no, $path, 'Database error on: '.$dav_data_insert ); - + $qry->QDo('SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $dav_data_params[':dav_name'])); if ( $qry->rows() == 1 && $row = $qry->Fetch() ) { $dav_id = $row->dav_id; } - + $vcard->Write( $row->dav_id, false ); - + if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit(); } - + if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) { if ( ! $qry->Commit() ) rollback_on_error( $caldav_context, $user_no, $path, 'Database error on COMMIT'); } - + } + /** * This function will import a whole calendar * @param string $ics_content the ics file to import @@ -803,14 +808,14 @@ function import_calendar_collection( $ics_content, $user_no, $path, $caldav_cont $after = $after->epoch(); } } - + $displayname = $calendar->GetPValue('X-WR-CALNAME'); if ( !$appending && isset($displayname) ) { $sql = 'UPDATE collection SET dav_displayname = :displayname WHERE dav_name = :dav_name'; $qry = new AwlQuery( $sql, array( ':displayname' => $displayname, ':dav_name' => $path) ); if ( ! $qry->Exec('PUT',__LINE__,__FILE__) ) rollback_on_error( $caldav_context, $user_no, $path, 'Database error on: '.$sql ); } - + $tz_ids = array(); foreach( $timezones AS $k => $tz ) { @@ -846,7 +851,7 @@ function import_calendar_collection( $ics_content, $user_no, $path, $caldav_cont } $collection = $qry->Fetch(); $collection_id = $collection->collection_id; - + // Fetch the current collection data $qry->QDo('SELECT dav_name, caldav_data FROM caldav_data WHERE collection_id=:collection_id', array( ':collection_id' => $collection_id @@ -854,7 +859,7 @@ function import_calendar_collection( $ics_content, $user_no, $path, $caldav_cont $current_data = array(); while( $row = $qry->Fetch() ) $current_data[$row->dav_name] = $row->caldav_data; - + if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) $qry->Begin(); $base_params = array( ':collection_id' => $collection_id ); @@ -867,7 +872,7 @@ EOSQL; UPDATE caldav_data SET user_no=:user_no, caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user, modified=current_timestamp WHERE collection_id=:collection_id AND dav_name=:dav_name EOSQL; - + $calitem_insert = << 0 ) $qry->QDo('SELECT new_sync_token(0,'.$collection_id.')'); - + foreach( $resources AS $uid => $resource ) { /** Construct the VCALENDAR data */ @@ -895,9 +900,9 @@ EOSQL; $vcal->SetComponents($resource); $icalendar = $vcal->Render(); $dav_name = sprintf( '%s%s.ics', $path, preg_replace('{[&?\\/@%+:]}','',$uid) ); - + if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin(); - + /** As ever, we mostly deal with the first resource component */ $first = $resource[0]; @@ -1061,7 +1066,7 @@ EOSQL; write_attendees($dav_id, $vcal); $qry->QDo("SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(':dav_name' => $dav_name ) ); - + do_scheduling_requests( $vcal, true ); if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit(); } @@ -1076,7 +1081,7 @@ EOSQL; } if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit(); } - + if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) { if ( ! $qry->Commit() ) rollback_on_error( $caldav_context, $user_no, $path); } @@ -1322,7 +1327,7 @@ function write_resource( DAVResource $resource, $caldav_data, DAVResource $colle $created = $first->GetPValue('CREATED'); if ( $created == '00001231T000000Z' ) $created = '20001231T000000Z'; - + $class = $first->GetPValue('CLASS'); /* Check and see if we should over ride the class. */ /** @todo is there some way we can move this out of this function? Or at least get rid of the need for the SQL query here. */ @@ -1425,7 +1430,7 @@ function write_resource( DAVResource $resource, $caldav_data, DAVResource $colle return false; } - + if ( $put_action_type == 'INSERT' ) { $sql = <<GetPValue('UID'), $user_no, $collection_id, $path ); } - + $qry = new AwlQuery( $sql, $calitem_params ); if ( !$qry->Exec('PUT',__LINE__,__FILE__) ) { rollback_on_error( $caldav_context, $user_no, $path); @@ -1477,12 +1482,12 @@ EOSQL; if ( function_exists('post_commit_action') ) { post_commit_action( $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path ); } - + // Uncache anything to do with the collection $cache = getCacheInstance(); $cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $path); $cache->delete( $cache_ns, null ); - + dbg_error_log( 'PUT', 'User: %d, ETag: %s, Path: %s', $author, $etag, $path); return true; // Success! diff --git a/inc/schedule-functions.php b/inc/schedule-functions.php index 030ea60a..43563ce4 100644 --- a/inc/schedule-functions.php +++ b/inc/schedule-functions.php @@ -18,7 +18,7 @@ require_once('RRule-v2.php'); * - We don't do scheduling (disabled, no organizer, ...) * - We are an ATTENDEE declining the meeting. * - We are the ORGANIZER canceling the meeting. - * + * * @param DAVResource $deleted_resource The resource which has already been deleted */ function do_scheduling_for_delete(DAVResource $deleted_resource ) { @@ -28,10 +28,10 @@ function do_scheduling_for_delete(DAVResource $deleted_resource ) { if ( !isset($request) || (isset($c->enable_auto_schedule) && !$c->enable_auto_schedule) ) return true; if ( $deleted_resource->IsInSchedulingCollection() ) return true; - + $caldav_data = $deleted_resource->GetProperty('dav-data'); if ( empty($caldav_data) ) return true; - + $vcal = new vCalendar($caldav_data); $organizer = $vcal->GetOrganizer(); if ( $organizer === false || empty($organizer) ) { @@ -43,7 +43,7 @@ function do_scheduling_for_delete(DAVResource $deleted_resource ) { return true; } $organizer_email = preg_replace( '/^mailto:/i', '', $organizer->Value() ); - + if ( $request->principal->email() == $organizer_email ) { return doItipOrganizerCancel( $vcal ); } @@ -54,7 +54,7 @@ function do_scheduling_for_delete(DAVResource $deleted_resource ) { } return doItipAttendeeReply( $vcal, 'DECLINED', $request->principal->email()); } - + } @@ -66,11 +66,11 @@ function do_scheduling_for_delete(DAVResource $deleted_resource ) { //function do_scheduling_reply( vCalendar $vcal, vProperty $organizer ) { function doItipAttendeeReply( vCalendar $resource, $partstat ) { global $request; - + $organizer = $resource->GetOrganizer(); $organizer_email = preg_replace( '/^mailto:/i', '', $organizer->Value() ); $organizer_principal = new Principal('email',$organizer_email ); - + if ( !$organizer_principal->Exists() ) { dbg_error_log( 'schedule', 'Unknown ORGANIZER "%s" - unable to notify.', $organizer->Value() ); //TODO: header( "Debug: Could maybe do the iMIP message dance for organizer ". $organizer->Value() ); @@ -95,7 +95,7 @@ function doItipAttendeeReply( vCalendar $resource, $partstat ) { $collection_path = preg_replace('{/[^/]+$}', '/', $row->dav_name ); $segment_name = str_replace($collection_path, '', $row->dav_name ); $vcal = new vCalendar($row->caldav_data); - + $attendees = $vcal->GetAttendees(); foreach( $attendees AS $v ) { $email = preg_replace( '/^mailto:/i', '', $v->Value() ); @@ -118,7 +118,7 @@ function doItipAttendeeReply( vCalendar $resource, $partstat ) { $schedule_reply = GetItip(new vCalendar($vcal->Render(null, true)), 'REPLY', $attendee->Value(), array('CUTYPE'=>true, 'SCHEDULE-STATUS'=>true)); $schedule_request = GetItip(new vCalendar($row->caldav_data), 'REQUEST', null); - + dbg_error_log( 'schedule', 'Writing ATTENDEE scheduling REPLY from %s to %s', $request->principal->email(), $organizer_principal->email() ); $response = '3.7'; // Organizer was not found on server. @@ -209,13 +209,14 @@ function GetItip( VCalendar $vcal, $method, $attendee_value, $clear_attendee_par return $iTIP; } + /** * Handles sending the iTIP CANCEL messages to each ATTENDEE by the ORGANIZER. * @param vCalendar $vcal What's being cancelled. */ function doItipOrganizerCancel( vCalendar $vcal ) { global $request; - + $attendees = $vcal->GetAttendees(); if ( count($attendees) == 0 && count($old_attendees) == 0 ) { dbg_error_log( 'schedule', 'Event has no attendees - no scheduling required.', count($attendees) ); @@ -226,14 +227,14 @@ function doItipOrganizerCancel( vCalendar $vcal ) { $scheduling_actions = false; $iTIP = GetItip($vcal, 'CANCEL', null); - + foreach( $attendees AS $attendee ) { $email = preg_replace( '/^mailto:/i', '', $attendee->Value() ); if ( $email == $request->principal->email() ) { dbg_error_log( 'schedule', "not delivering to owner '%s'", $request->principal->email() ); continue; } - + $agent = $attendee->GetParameterValue('SCHEDULE-AGENT'); if ( $agent && $agent != 'SERVER' ) { dbg_error_log( 'schedule', "not delivering to %s, schedule agent set to value other than server", $email ); @@ -268,6 +269,7 @@ function doItipOrganizerCancel( vCalendar $vcal ) { return true; } + /** * Does the actual processing of the iTIP CANCEL message on behalf of an ATTENDEE, * which generally means writing it into the ATTENDEE's default calendar. @@ -278,7 +280,7 @@ function doItipOrganizerCancel( vCalendar $vcal ) { */ function processItipCancel( vCalendar $vcal, vProperty $attendee, WritableCollection $attendee_calendar, Principal $attendee_principal ) { global $request; - + dbg_error_log( 'schedule', 'Processing iTIP CANCEL to %s', $attendee->Value()); //TODO: header( "Debug: Could maybe do the iMIP message dance for attendee ". $attendee->Value() ); if ( !$attendee_calendar->Exists() ) { @@ -307,7 +309,7 @@ function processItipCancel( vCalendar $vcal, vProperty $attendee, WritableCollec return '1.2'; } $row = $qry->Fetch(); - + if ( $attendee_calendar->actualDeleteCalendarMember($row->dav_name) === false ) { dbg_error_log('ERROR', 'Could not delete calendar member %s for %s', $row->dav_name(), $attendee->Value()); @@ -316,7 +318,7 @@ function processItipCancel( vCalendar $vcal, vProperty $attendee, WritableCollec } return '1.2'; // Scheduling invitation delivered successfully - + } @@ -385,7 +387,7 @@ EOTEMPLATE; } } $mime->addPart( $friendly_part, 'text/plain' ); - + $email = new EMail(); $email->SetFrom($request->principal->email()); $email->AddTo($to_email);