Checkpoint the current code for scheduling on DELETE .

This commit is contained in:
Andrew McMillan 2011-11-02 01:30:45 +13:00
parent 3001207904
commit d8d16c8ee9
16 changed files with 784 additions and 40 deletions

View File

@ -1059,8 +1059,8 @@ EOQRY;
/**
* Checks whether this resource is a calendar
* @param string $type The type of scheduling collection, 'read', 'write' or 'any'
* Checks whether this resource is a scheduling inbox/outbox collection
* @param string $type The type of scheduling collection, 'inbox', 'outbox' or 'any'
*/
function IsSchedulingCollection( $type = 'any' ) {
if ( $this->_is_collection && preg_match( '{schedule-(inbox|outbox)}', $this->collection->type, $matches ) ) {
@ -1070,6 +1070,18 @@ EOQRY;
}
/**
* Checks whether this resource is IN a scheduling inbox/outbox collection
* @param string $type The type of scheduling collection, 'inbox', 'outbox' or 'any'
*/
function IsInSchedulingCollection( $type = 'any' ) {
if ( !$this->_is_collection && preg_match( '{schedule-(inbox|outbox)}', $this->collection->type, $matches ) ) {
return ($type == 'any' || $type == $matches[1]);
}
return false;
}
/**
* Checks whether this resource is an addressbook
*/
@ -1256,6 +1268,14 @@ EOQRY;
}
/**
* Checks whether the target collection is for public events only
*/
function IsPublicOnly() {
return ( isset($this->collection->publicly_events_only) && $this->collection->publicly_events_only == 't' );
}
/**
* Return the type of whatever contains this resource, or would if it existed.
*/
@ -1382,6 +1402,11 @@ EOQRY;
return clone($this->resource);
break;
case 'dav-data':
if ( !isset($this->resource) ) $this->FetchResource();
return $this->resource->caldav_data;
break;
case 'principal':
if ( !isset($this->principal) ) $this->FetchPrincipal();
return clone($this->principal);

View File

@ -17,7 +17,7 @@ class WritableCollection extends DAVResource {
return $p->GetParameterValue('TZID');
}
/**
/**
* Writes the data to a member in the collection and returns the segment_name of the
* resource in our internal namespace.
*
@ -36,7 +36,7 @@ class WritableCollection extends DAVResource {
return false;
}
global $tz_regex, $session, $caldav_context;
global $session, $caldav_context;
$resources = $vcal->GetComponents('VTIMEZONE',false); // Not matching VTIMEZONE
$user_no = $this->user_no();
@ -179,18 +179,11 @@ class WritableCollection extends DAVResource {
$calitem_params[':dtstamp'] = $dtstamp;
$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. */
if ( public_events_only($user_no, $path) ) {
$class = 'PUBLIC';
}
/*
* It seems that some calendar clients don't set a class...
* RFC2445, 4.8.1.3:
* Default is PUBLIC
* RFC2445, 4.8.1.3: Default is PUBLIC
*/
if ( !isset($class) || $class == '' ) {
if ( $this->IsPublicOnly() || !isset($class) || $class == '' ) {
$class = 'PUBLIC';
}
$calitem_params[':class'] = $class;
@ -202,19 +195,11 @@ class WritableCollection extends DAVResource {
$tz = $vcal->GetTimeZone($tzid);
$olson = $vcal->GetOlsonName($tz);
dbg_error_log( 'PUT', ' Using TZID[%s] and location of [%s]', $tzid, (isset($olson) ? $olson : '') );
if ( !empty($olson) && ($olson != $last_olson) && preg_match( $tz_regex, $olson ) ) {
if ( !empty($olson) && ($olson != $last_olson) ) {
dbg_error_log( 'PUT', ' Setting timezone to %s', $olson );
$qry->QDo('SET TIMEZONE TO \''.$olson."'" );
$last_olson = $olson;
}
$params = array( ':tzid' => $tzid);
$qry = new AwlQuery('SELECT 1 FROM timezones WHERE tzid = :tzid', $params );
if ( $qry->Exec('PUT',__LINE__,__FILE__) && $qry->rows() == 0 ) {
$params[':olson_name'] = $olson;
$params[':vtimezone'] = (isset($tz) ? $tz->Render() : null );
$qry->QDo('INSERT INTO timezones (tzid, olson_name, active, vtimezone) VALUES(:tzid,:olson_name,false,:vtimezone)', $params );
}
}
$created = $first->GetPValue('CREATED');
@ -258,8 +243,8 @@ EOSQL;
}
if ( !$this->IsSchedulingCollection() ) {
write_alarms($dav_id, $first);
write_attendees($dav_id, $vcal);
$this->WriteCalendarAlarms($dav_id, $vcal);
$this->WriteCalendarAttendees($dav_id, $vcal);
if ( $log_action && function_exists('log_caldav_action') ) {
log_caldav_action( $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path );
}
@ -317,4 +302,144 @@ EOSQL;
return $segment_name;
}
/**
* Given a dav_id and an original vCalendar, pull out each of the VALARMs
* and write the values into the calendar_alarm table.
*
* @return null
*/
function WriteCalendarAlarms( $dav_id, vCalendar $vcal ) {
$qry = new AwlQuery('DELETE FROM calendar_alarm WHERE dav_id = '.$dav_id );
$qry->Exec('PUT',__LINE__,__FILE__);
$components = $vcal->GetComponents();
$qry->SetSql('INSERT INTO calendar_alarm ( dav_id, action, trigger, summary, description, component, next_trigger )
VALUES( '.$dav_id.', :action, :trigger, :summary, :description, :component,
:related::timestamp with time zone + :related_trigger::interval )' );
$qry->Prepare();
foreach( $components AS $component ) {
if ( $component->GetType() == 'VTIMEZONE' ) continue;
$alarms = $component->GetComponents('VALARM');
if ( count($alarms) < 1 ) return;
foreach( $alarms AS $v ) {
$trigger = array_merge($v->GetProperties('TRIGGER'));
if ( $trigger == null ) continue; // Bogus data.
$trigger = $trigger[0];
$related = null;
$related_trigger = '0M';
$trigger_type = $trigger->GetParameterValue('VALUE');
if ( !isset($trigger_type) || $trigger_type == 'DURATION' ) {
switch ( $trigger->GetParameterValue('RELATED') ) {
case 'DTEND': $related = $component->GetPValue('DTEND'); break;
case 'DUE': $related = $component->GetPValue('DUE'); break;
default: $related = $component->GetPValue('DTSTART');
}
$duration = $trigger->Value();
if ( !preg_match('{^-?P(:?\d+W)?(:?\d+D)?(:?T(:?\d+H)?(:?\d+M)?(:?\d+S)?)?$}', $duration ) ) continue;
$minus = (substr($duration,0,1) == '-');
$related_trigger = trim(preg_replace( '#[PT-]#', ' ', $duration ));
if ( $minus ) {
$related_trigger = preg_replace( '{(\d+[WDHMS])}', '-$1 ', $related_trigger );
}
else {
$related_trigger = preg_replace( '{(\d+[WDHMS])}', '$1 ', $related_trigger );
}
}
else {
if ( false === strtotime($trigger->Value()) ) continue; // Invalid date.
}
$qry->Bind(':action', $v->GetPValue('ACTION'));
$qry->Bind(':trigger', $trigger->Render());
$qry->Bind(':summary', $v->GetPValue('SUMMARY'));
$qry->Bind(':description', $v->GetPValue('DESCRIPTION'));
$qry->Bind(':component', $v->Render());
$qry->Bind(':related', $related );
$qry->Bind(':related_trigger', $related_trigger );
$qry->Exec('PUT',__LINE__,__FILE__);
}
}
}
/**
* Parse out the attendee property and write a row to the
* calendar_attendee table for each one.
* @param int $dav_id The dav_id of the caldav_data we're processing
* @param vComponent The VEVENT or VTODO containing the ATTENDEEs
* @return null
*/
function WriteCalendarAttendees( $dav_id, vCalendar $vcal ) {
$qry = new AwlQuery('DELETE FROM calendar_attendee WHERE dav_id = '.$dav_id );
$qry->Exec('PUT',__LINE__,__FILE__);
$attendees = $vcal->GetAttendees();
if ( count($attendees) < 1 ) return;
$qry->SetSql('INSERT INTO calendar_attendee ( dav_id, status, partstat, cn, attendee, role, rsvp, property )
VALUES( '.$dav_id.', :status, :partstat, :cn, :attendee, :role, :rsvp, :property )' );
$qry->Prepare();
$processed = array();
foreach( $attendees AS $v ) {
$attendee = $v->Value();
if ( isset($processed[$attendee]) ) {
dbg_error_log( 'LOG', 'Duplicate attendee "%s" in resource "%d"', $attendee, $dav_id );
dbg_error_log( 'LOG', 'Original: "%s"', $processed[$attendee] );
dbg_error_log( 'LOG', 'Duplicate: "%s"', $v->Render() );
continue; /** @todo work out why we get duplicate ATTENDEE on one VEVENT */
}
$qry->Bind(':attendee', $attendee );
$qry->Bind(':status', $v->GetParameterValue('STATUS') );
$qry->Bind(':partstat', $v->GetParameterValue('PARTSTAT') );
$qry->Bind(':cn', $v->GetParameterValue('CN') );
$qry->Bind(':role', $v->GetParameterValue('ROLE') );
$qry->Bind(':rsvp', $v->GetParameterValue('RSVP') );
$qry->Bind(':property', $v->Render() );
$qry->Exec('PUT',__LINE__,__FILE__);
$processed[$attendee] = $v->Render();
}
}
/**
* Writes the data to a member in the collection and returns the segment_name of the
* resource in our internal namespace.
*
* @param vCalendar $member_dav_name The path to the resource to be deleted.
* @return boolean Success is true, or false on failure.
*/
function actualDeleteCalendarMember( $member_dav_name ) {
global $session, $caldav_context;
// A quick sanity check...
$segment_name = str_replace( $this->dav_name(), '', $member_dav_name );
if ( strstr($segment_name, '/') !== false ) {
@dbg_error_log( "DELETE", "DELETE: Refused to delete member '%s' from calendar '%s'!", $member_dav_name, $this->dav_name() );
return false;
}
// We need to serialise access to this process just for this collection
$cache = getCacheInstance();
$myLock = $cache->acquireLock('collection-'.$this->dav_name());
$qry = new AwlQuery();
$params = array( ':dav_name' => $member_dav_name );
if ( $qry->QDo("SELECT write_sync_change(collection_id, 404, caldav_data.dav_name) FROM caldav_data WHERE dav_name = :dav_name", $params )
&& $qry->QDo("DELETE FROM property WHERE dav_name = :dav_name", $params )
&& $qry->QDo("DELETE FROM locks WHERE dav_name = :dav_name", $params )
&& $qry->QDo("DELETE FROM caldav_data WHERE dav_name = :dav_name", $params ) ) {
@dbg_error_log( "DELETE", "DELETE: Calendar member %s deleted from calendar '%s'", $member_dav_name, $this->dav_name() );
$cache->releaseLock($myLock);
return true;
}
$cache->releaseLock($myLock);
return false;
}
}

View File

@ -17,6 +17,7 @@ $container->NeedPrivilege('DAV::unbind');
$lock_opener = $request->FailIfLocked();
require_once('schedule-functions.php');
function delete_collection( $id ) {
$params = array( ':collection_id' => $id );
@ -72,12 +73,14 @@ else {
$request->DoResponse( 412, translate("Resource has changed on server - not deleted") );
}
$params = array( ':dav_id' => $dav_resource->resource_id() );
// Check to see if we need to do any scheduling transactions for this one.
do_scheduling_for_delete($dav_resource);
// We need to serialise access to this process just for this collection
$cache = getCacheInstance();
$myLock = $cache->acquireLock('collection-'.$dav_resource->parent_path());
$params = array( ':dav_id' => $dav_resource->resource_id() );
if ( $qry->QDo("SELECT write_sync_change(collection_id, 404, caldav_data.dav_name) FROM caldav_data WHERE dav_id = :dav_id", $params )
&& $qry->QDo("DELETE FROM property WHERE dav_name = (SELECT dav_name FROM caldav_data WHERE dav_id = :dav_id)", $params )
&& $qry->QDo("DELETE FROM locks WHERE dav_name = (SELECT dav_name FROM caldav_data WHERE dav_id = :dav_id)", $params )
@ -92,7 +95,6 @@ else {
$request->DoResponse( 204 );
}
$cache->releaseLock($myLock);
}
$request->DoResponse( 500 );

310
inc/schedule-functions.php Normal file
View File

@ -0,0 +1,310 @@
<?php
/**
* Functions for handling CalDAV Scheduling.
*
* @package davical
* @subpackage caldav
* @author Andrew McMillan <andrew@morphoss.com>
* @copyright Morphoss Ltd - http://www.morphoss.com/
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later version
*/
require_once('vCalendar.php');
require_once('WritableCollection.php');
require_once('RRule-v2.php');
/**
* Entry point for scheduling on DELETE, for which there are thee outcomes:
* - 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 ) {
// By the time we arrive here the resource *has* actually been deleted from disk
// we can only fail to (de-)schedule the activity...
global $request, $c;
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) ) {
dbg_error_log( 'schedule', 'Event has no organizer - no scheduling required.' );
return true;
}
if ( $vcal->GetScheduleAgent() != 'SERVER' ) {
dbg_error_log( 'schedule', 'SCHEDULE-AGENT=%s - no scheduling required.', $vcal->GetScheduleAgent() );
return true;
}
$organizer_email = preg_replace( '/^mailto:/i', '', $organizer->Value() );
if ( $request->principal->email() == $organizer_email ) {
return doItipOrganizerCancel( $vcal );
}
else {
if ( isset($_SERVER['HTTP_SCHEDULE_REPLY']) && $_SERVER['HTTP_SCHEDULE_REPLY'] == 'F') {
dbg_error_log( 'schedule', 'Schedule-Request header set to "F" - no scheduling required.' );
return true;
}
return doItipAttendeeReply( $vcal, 'DECLINED', $request->principal->email());
}
}
/**
* Do the scheduling adjustments for a REPLY when an ATTENDEE updates their status.
* @param vCalendar $vcal The resource that the ATTENDEE is writing to their calendar
* @param string $organizer The property which is the event ORGANIZER.
*/
//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 );
$sql = 'SELECT caldav_data.dav_name, caldav_data.caldav_data FROM caldav_data JOIN calendar_item USING(dav_id) ';
$sql .= 'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
$sql .= 'AND uid=? LIMIT 1';
$uids = $resource->GetPropertiesByPath('/VCALENDAR/*/UID');
if ( count($uids) == 0 ) {
dbg_error_log( 'schedule', 'No UID in VCALENDAR - giving up on REPLY.' );
return true;
}
$uid = $uids[0]->Value();
$qry = new AwlQuery($sql,$organizer_principal->user_no(), $uid);
if ( !$qry->Exec('schedule',__LINE__,__FILE__) || $qry->rows() < 1 ) {
dbg_error_log( 'schedule', 'Could not find original event from organizer - giving up on REPLY.' );
return true;
}
$row = $qry->Fetch();
$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() );
if ( $email == $request->principal->email() ) {
$attendee = $v;
break;
}
}
if ( empty($attendee) ) {
dbg_error_log( 'schedule', 'Could not find ATTENDEE in VEVENT - giving up on REPLY.' );
return true;
}
$attendee->SetParameterValue('PARTSTAT', $partstat);
$attendee->SetParameterValue('SCHEDULE-STATUS', '2.0');
$vcal->UpdateAttendeeStatus($request->principal->email(), clone($attendee) );
$organizer_calendar = new WritableCollection(array('path' => $collection_path));
$organizer_inbox = new WritableCollection(array('path' => $organizer_principal->internal_url('schedule-inbox')));
$schedule_reply = GetItip(new vCalendar($row->caldav_data),'REPLY',$attendee->Value());
$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.
if ( !$organizer_calendar->Exists() ) {
dbg_error_log('ERROR','Default calendar at "%s" does not exist for user "%s"',
$organizer_calendar->dav_name(), $schedule_target->username());
$response = '5.2'; // No scheduling support for user
}
else {
if ( ! $organizer_inbox->HavePrivilegeTo('schedule-deliver-reply') ) {
$response = '3.8'; // No authority to deliver replies to organizer.
}
$response = '1.2'; // Scheduling reply delivered successfully
if ( $organizer_calendar->WriteCalendarMember($vcal, false, false, $segment_name) === false ) {
dbg_error_log('ERROR','Could not write updated calendar member to %s', $attendee_calendar->dav_name() );
trace_bug('Failed to write scheduling resource.');
}
$organizer_inbox->WriteCalendarMember($schedule_reply, false, false, $request->principal->username().$segment_name);
}
dbg_error_log( 'schedule', 'Status for organizer <%s> set to "%s"', $organizer->Value(), $response );
$organizer->SetParameterValue( 'SCHEDULE-STATUS', $response );
$resource->UpdateOrganizerStatus($organizer); // Which was passed in by reference, and we're updating it here.
// Now we loop through the *other* ATTENDEEs, updating them on the status of the ATTENDEE DECLINE/ACCEPT
foreach( $attendees AS $attendee ) {
$email = preg_replace( '/^mailto:/i', '', $attendee->Value() );
if ( $email == $request->principal->email() || $email == $organizer_principal->email() ) continue;
$agent = $attendee->GetParameterValue('SCHEDULE-AGENT');
if ( !empty($agent) && $agent != 'SERVER' ) continue;
$schedule_target = new Principal('email',$email);
if ( $schedule_target->Exists() ) {
$attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
if ( !$attendee_calendar->Exists() ) {
dbg_error_log('ERROR','Default calendar at "%s" does not exist for user "%s"',
$attendee_calendar->dav_name(), $schedule_target->username());
continue;
}
else {
$attendee_inbox = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-inbox')));
if ( ! $attendee_inbox->HavePrivilegeTo('schedule-deliver-invite') ) continue;
if ( $attendee_calendar->WriteCalendarMember($vcal, false) === false ) {
dbg_error_log('ERROR','Could not write updated calendar member to %s', $attendee_calendar->dav_name());
trace_bug('Failed to write scheduling resource.');
}
$attendee_inbox->WriteCalendarMember($schedule_request, false);
}
}
}
return true;
}
function GetItip( VCalendar $vcal, $method, $attendee_value ) {
$iTIP = $vcal->GetItip($method, $attendee_value );
$iTIP->AddProperty('REQUEST-STATUS','2.0');
$components = $iTIP->GetComponents();
foreach( $components AS $comp ) {
$properties = array();
foreach( $comp->GetProperties() AS $k=> $property ) {
switch( $property->Name() ) {
case 'DTSTART':
case 'DTEND':
case 'DUE':
$when = new RepeatRuleDateTime($property);
$properties[] = new vProperty( $property->Name() . ":" . $when->UTC() );
break;
default:
$properties[] = $property;
}
}
$comp->SetProperties($properties);
}
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) );
return true;
}
dbg_error_log( 'schedule', 'Writing scheduling resources for %d attendees', count($attendees) );
$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 );
continue;
}
$schedule_target = new Principal('email',$email);
if ( !$schedule_target->Exists() ) {
$response = '3.7';
}
else {
$attendee_inbox = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-inbox')));
if ( ! $attendee_inbox->HavePrivilegeTo('schedule-deliver-invite') ) {
dbg_error_log( 'schedule', "No authority to deliver invite to %s", $schedule_target->internal_url('schedule-inbox') );
$response = '3.8';
}
else {
$attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
$response = processItipCancel( $vcal, $attendee, $attendee_calendar, $schedule_target );
deliverItipCancel( $iTIP, $attendee, $attendee_inbox );
}
}
dbg_error_log( 'schedule', 'Status for attendee <%s> set to "%s"', $attendee->Value(), $response );
$attendee->SetParameterValue( 'SCHEDULE-STATUS', $response );
$scheduling_actions = true;
}
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.
*
* @param vCalendar $vcal The message.
* @param vProperty $attendee
* @param WritableCollection $attendee_calendar
*/
function processItipCancel( vCalendar $vcal, vProperty $attendee, WritableCollection $attendee_calendar, Principal $attendee_principal ) {
dbg_error_log( 'schedule', 'Processing iTIP CANCEL to %s', $attendee->Value());
if ( !$attendee_calendar->Exists() ) {
dbg_error_log('ERROR', 'Default calendar at "%s" does not exist for attendee "%s"',
$attendee_calendar->dav_name(), $attendee->Value());
return '5.2'; // No scheduling support for user
}
$sql = 'SELECT caldav_data.dav_name FROM caldav_data JOIN calendar_item USING(dav_id) ';
$sql .= 'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
$sql .= 'AND uid=? LIMIT 1';
$uids = $vcal->GetPropertiesByPath('/VCALENDAR/*/UID');
if ( count($uids) == 0 ) {
dbg_error_log( 'schedule', 'No UID in VCALENDAR - giving up on CANCEL processing.' );
return '3.8';
}
$uid = $uids[0]->Value();
$qry = new AwlQuery($sql, $attendee_principal->user_no(), $uid);
if ( !$qry->Exec('schedule',__LINE__,__FILE__) || $qry->rows() < 1 ) {
dbg_error_log( 'schedule', 'Could not find ATTENDEE copy of original event - not trying to DELETE it!' );
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());
trace_bug('Failed to write scheduling resource.');
return '5.2';
}
return '1.2'; // Scheduling invitation delivered successfully
}
/**
* Delivers the iTIP CANCEL message to an ATTENDEE's Scheduling Inbox Collection.
*
* This is pretty simple at present, but could be extended in the future to do the sending
* of e-mail to a remote attendee.
*
* @param vCalendar $iTIP
* @param vProperty $attendee
* @param WritableCollection $attendee_inbox
*/
function deliverItipCancel( vCalendar $iTIP, vProperty $attendee, WritableCollection $attendee_inbox ) {
$attendee_inbox->WriteCalendarMember($iTIP, false);
}

View File

@ -29,16 +29,16 @@ BEGIN:VEVENT
CREATED:20111018T195845Z
UID:E1A13F04-iCal-schedule
DTEND;TZID=Pacific/Auckland:20111019T110000
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
ATTENDEE;CN=user1@example.net;CUTYPE=INDIVIDUAL;EMAIL=user1@example.net;
PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:user1@example.net
TRANSP:OPAQUE
SUMMARY:Meeting with User1
DTSTART;TZID=Pacific/Auckland:20111019T100000
DTSTAMP:20111018T200107Z
ORGANIZER;CN="Manager 1":mailto:manager1@example.net
SEQUENCE:5
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
ATTENDEE;CN=user1@example.net;CUTYPE=INDIVIDUAL;EMAIL=user1@example.net;
PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:user1@example.net
END:VEVENT
END:VCALENDAR
<
@ -118,16 +118,16 @@ BEGIN:VEVENT
CREATED:20111018T195845Z
UID:E1A13F04-iCal-schedule
DTEND;TZID=Pacific/Auckland:20111019T110000
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
ATTENDEE;CN=user1@example.net;CUTYPE=INDIVIDUAL;EMAIL=user1@example.net;
PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:user1@example.net
TRANSP:OPAQUE
SUMMARY:Meeting with User1
DTSTART;TZID=Pacific/Auckland:20111019T100000
DTSTAMP:20111018T200107Z
ORGANIZER;CN="Manager 1":mailto:manager1@example.net
SEQUENCE:5
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
ATTENDEE;CN=user1@example.net;CUTYPE=INDIVIDUAL;EMAIL=user1@example.net;
PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:user1@example.net
END:VEVENT
END:VCALENDAR
<

View File

@ -127,16 +127,16 @@ BEGIN:VEVENT
CREATED:20111018T195845Z
UID:E1A13F04-iCal-schedule
DTEND;TZID=Pacific/Auckland:20111019T110000
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
ATTENDEE;CN=user1@example.net;CUTYPE=INDIVIDUAL;EMAIL=user1@example.net;
PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:user1@example.net
TRANSP:OPAQUE
SUMMARY:Meeting with User1
DTSTART;TZID=Pacific/Auckland:20111019T100000
DTSTAMP:20111018T200107Z
ORGANIZER;CN="Manager 1":mailto:manager1@example.net
SEQUENCE:5
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
ATTENDEE;CN=user1@example.net;CUTYPE=INDIVIDUAL;EMAIL=user1@example.net;
PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:user1@example.net
END:VEVENT
END:VCALENDAR
<

View File

@ -5,7 +5,7 @@ TYPE=PUT
URL=http://regression.host/caldav.php/manager1/home/E1A13F04-iCal-schedule.ics
HEADER=Content-Type: text/calendar
HEADER=DAVKit/4.0.3 (732.2); CalendarStore/4.0.4 (997.7); iCal/4.0.4 (1395.7); Mac OS X/10.6.8 (10K549)
HEADER=If-Match: "87421a4ff0b84a95a31db428ee6d11d9"
HEADER=If-Match: "d60f8959edc5eee6e949a2e5b81dd746"
HEAD
AUTH=manager1:manager1

View File

@ -0,0 +1,14 @@
HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, calendar-proxy, bind, addressbook, calendar-auto-schedule
Content-Length: 0
Content-Type: text/plain; charset="utf-8"
dav_name: >/manager1/home/E1A13F04-iCal-schedule.ics<
dav_name: >/user1/home/E1A13F04-iCal-schedule.ics<
dav_name: >/manager1/.in/user1E1A13F04-iCal-schedule.ics<

View File

@ -0,0 +1,18 @@
#
# iCal DELETE's the invitation in the .in
#
TYPE=DELETE
URL=http://regression.host/user1/.in/E1A13F04-iCal-schedule.ics
HEADER=User-Agent: DAVKit/4.0.3 (732.2); CalendarStore/4.0.4 (997.7); iCal/4.0.4 (1395.7); Mac OS X/10.6.8 (10K549)
HEAD
#
# Query to confirm we got rid of it. There should be two now.
QUERY
SELECT dav_name
FROM calendar_item
WHERE uid = 'E1A13F04-iCal-schedule'
ORDER BY dav_id
ENDQUERY

View File

@ -0,0 +1,12 @@
HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, calendar-proxy, bind, addressbook, calendar-auto-schedule
Content-Length: 0
Content-Type: text/plain; charset="utf-8"
dav_name: >/manager1/home/E1A13F04-iCal-schedule.ics<
dav_name: >/user1/home/E1A13F04-iCal-schedule.ics<

View File

@ -0,0 +1,20 @@
#
# iCal DELETE's the invitation in the .in
#
TYPE=DELETE
URL=http://regression.host/manager1/.in/user1E1A13F04-iCal-schedule.ics
HEADER=User-Agent: DAVKit/4.0.3 (732.2); CalendarStore/4.0.4 (997.7); iCal/4.0.4 (1395.7); Mac OS X/10.6.8 (10K549)
HEAD
AUTH=manager1:manager1
#
# Query to confirm we got rid of it. There should be two now.
QUERY
SELECT dav_name
FROM calendar_item
WHERE uid = 'E1A13F04-iCal-schedule'
ORDER BY dav_id
ENDQUERY

View File

@ -0,0 +1,70 @@
HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, calendar-proxy, bind, addressbook, calendar-auto-schedule
Content-Length: 0
Content-Type: text/plain; charset="utf-8"
caldav_data: >BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//iCal 4.0.4//EN
CALSCALE:GREGORIAN
BEGIN:VTIMEZONE
TZID:Pacific/Auckland
BEGIN:DAYLIGHT
TZOFFSETFROM:+1200
RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1SU
DTSTART:20070930T020000
TZNAME:GMT+13:00
TZOFFSETTO:+1300
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+1300
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU
DTSTART:20080406T030000
TZNAME:GMT+12:00
TZOFFSETTO:+1200
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20111018T195845Z
UID:E1A13F04-iCal-schedule
DTEND;TZID=Pacific/Auckland:20111019T140000
TRANSP:OPAQUE
SUMMARY:Meeting with User1
DTSTART;TZID=Pacific/Auckland:20111019T130000
DTSTAMP:20111024T035702Z
ORGANIZER;CN="Manager 1":mailto:manager1@example.net
SEQUENCE:6
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
ATTENDEE;CN=user1@example.net;CUTYPE=INDIVIDUAL;EMAIL=user1@example.net;
PARTSTAT=DECLINED;ROLE=REQ-PARTICIPANT;RSVP=TRUE;SCHEDULE-STATUS=2.0:mai
lto:user1@example.net
END:VEVENT
END:VCALENDAR
<
dav_name: >/manager1/home/E1A13F04-iCal-schedule.ics<
caldav_data: >BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//iCal 4.0.4//EN
CALSCALE:GREGORIAN
METHOD:REPLY
REQUEST-STATUS:2.0
BEGIN:VEVENT
UID:E1A13F04-iCal-schedule
DTEND:20111019T010000Z
ATTENDEE;CN=user1@example.net;CUTYPE=INDIVIDUAL;EMAIL=user1@example.net;
PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;SCHEDULE-STATUS=1.2
:mailto:user1@example.net
DTSTART:20111019T000000Z
ORGANIZER;CN="Manager 1":mailto:manager1@example.net
SEQUENCE:7
DTSTAMP:20111102T010804Z
END:VEVENT
END:VCALENDAR
<
dav_name: >/manager1/.in/user1E1A13F04-iCal-schedule.ics<

View File

@ -0,0 +1,23 @@
#
# We now DELETE the ATTENDEE's copy of the actual event.
# - This should send a CANCEL reply and update the manager's
# event copy with the PARTSTAT=DECLINED
#
TYPE=DELETE
URL=http://regression.host/user1/home/E1A13F04-iCal-schedule.ics
HEADER=User-Agent: DAVKit/4.0.3 (732.2); CalendarStore/4.0.4 (997.7); iCal/4.0.4 (1395.7); Mac OS X/10.6.8 (10K549)
HEAD
#
# Query to confirm we got rid of it. There should be two now:
# - An event in the manager's calendar, with a PARTSTART=DECLINED for user 1
# - An iTIP message in the managers's inbox.
QUERY
SELECT calendar_item.dav_name,
caldav_data.caldav_data
FROM calendar_item JOIN caldav_data USING(dav_id, dav_name)
WHERE uid = 'E1A13F04-iCal-schedule'
ORDER BY dav_id
ENDQUERY

View File

@ -0,0 +1,21 @@
#
# Now DELETE's the cancelation reply in the Manager's .in
#
TYPE=DELETE
URL=http://regression.host/manager1/.in/user1E1A13F04-iCal-schedule.ics
HEADER=User-Agent: DAVKit/4.0.3 (732.2); CalendarStore/4.0.4 (997.7); iCal/4.0.4 (1395.7); Mac OS X/10.6.8 (10K549)
HEAD
AUTH=manager1:manager1
#
# Query to confirm we got rid of it. There should only be one
# lonely meeting in the manager's calendar (which has been declined).
QUERY
SELECT dav_name
FROM calendar_item
WHERE uid = 'E1A13F04-iCal-schedule'
ORDER BY dav_id
ENDQUERY

View File

@ -0,0 +1,73 @@
#
# PUT an event with several attendees - so we can delete the organizer
# copy in the next request.
#
# After this we should see 7 events: 1 manager, 3 attendees, 3 attendee iTIP
#
TYPE=PUT
URL=http://regression.host/caldav.php/manager1/home/E1A13F04-iCal-schedule.ics
HEADER=Content-Type: text/calendar
HEADER=DAVKit/4.0.3 (732.2); CalendarStore/4.0.4 (997.7); iCal/4.0.4 (1395.7); Mac OS X/10.6.8 (10K549)
HEADER=If-Match: "651df94a71cc99384231637a5df101f4"
HEAD
AUTH=manager1:manager1
BEGINDATA
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//iCal 4.0.4//EN
CALSCALE:GREGORIAN
BEGIN:VTIMEZONE
TZID:Pacific/Auckland
BEGIN:DAYLIGHT
TZOFFSETFROM:+1200
RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1SU
DTSTART:20070930T020000
TZNAME:GMT+13:00
TZOFFSETTO:+1300
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+1300
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU
DTSTART:20080406T030000
TZNAME:GMT+12:00
TZOFFSETTO:+1200
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20111018T195845Z
UID:E1A13F04-iCal-schedule
DTEND;TZID=Pacific/Auckland:20111019T140000
ATTENDEE;CN="Manager 1";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:manag
er1@example.net
ATTENDEE;CN="user1@example.net";CUTYPE=INDIVIDUAL;EMAIL="user1@example.n
et";PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user1@ex
ample.net
ATTENDEE;CN="user2@example.net";CUTYPE=INDIVIDUAL;EMAIL="user2@example.n
et";PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user2@ex
ample.net
ATTENDEE;CN="user3@example.net";CUTYPE=INDIVIDUAL;EMAIL="user3@example.n
et";PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user3@ex
ample.net
TRANSP:OPAQUE
SUMMARY:Meeting with User1
DTSTART;TZID=Pacific/Auckland:20111019T130000
DTSTAMP:20111024T035702Z
ORGANIZER;CN="Manager 1":mailto:manager1@example.net
SEQUENCE:7
END:VEVENT
END:VCALENDAR
ENDDATA
QUERY
SELECT caldav_data.user_no, caldav_data.dav_name,
caldav_type, logged_user, caldav_data.caldav_data AS "vcalendar",
summary
FROM caldav_data JOIN calendar_item USING(dav_name) LEFT JOIN timezones ON (tz_id=tzid)
WHERE calendar_item.uid = 'E1A13F04-iCal-schedule'
ORDER BY caldav_data.dav_id
ENDQUERY

View File

@ -0,0 +1,31 @@
#
# We now DELETE the ORGANIZER's copy of the actual event.
# - This should send a CANCEL reply and remove each attendee's
# copy of the event
#
TYPE=DELETE
URL=http://regression.host/manager1/home/E1A13F04-iCal-schedule.ics
HEADER=User-Agent: DAVKit/4.0.3 (732.2); CalendarStore/4.0.4 (997.7); iCal/4.0.4 (1395.7); Mac OS X/10.6.8 (10K549)
HEAD
AUTH=manager1:manager1
# Before we run, this time we'll assume everyone has read their inbox
# and all existing iTIP messages are deleted.
DOSQL
DELETE FROM caldav_data WHERE dav_name ~ E'/\\.in/.*E1A13F04-iCal-schedule\\.ics'
ENDDOSQL
#
# Query to confirm we got rid of it. There should be two now:
# - An event in the manager's calendar, with a PARTSTART=DECLINED for user 1
# - An iTIP message in the managers's inbox.
QUERY
SELECT calendar_item.dav_name,
caldav_data.caldav_data
FROM calendar_item JOIN caldav_data USING(dav_id, dav_name)
WHERE uid = 'E1A13F04-iCal-schedule'
ORDER BY dav_id
ENDQUERY