mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-05-26 02:44:29 +00:00
Checkpoint the current code for scheduling on DELETE .
This commit is contained in:
parent
3001207904
commit
d8d16c8ee9
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
310
inc/schedule-functions.php
Normal 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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
<
|
||||
|
||||
@ -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
|
||||
<
|
||||
|
||||
@ -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
|
||||
|
||||
14
testing/tests/scheduling/3028-DELETE-reply.result
Normal file
14
testing/tests/scheduling/3028-DELETE-reply.result
Normal 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<
|
||||
|
||||
18
testing/tests/scheduling/3028-DELETE-reply.test
Normal file
18
testing/tests/scheduling/3028-DELETE-reply.test
Normal 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
|
||||
|
||||
12
testing/tests/scheduling/3029-DELETE-reply.result
Normal file
12
testing/tests/scheduling/3029-DELETE-reply.result
Normal 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<
|
||||
|
||||
20
testing/tests/scheduling/3029-DELETE-reply.test
Normal file
20
testing/tests/scheduling/3029-DELETE-reply.test
Normal 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
|
||||
|
||||
70
testing/tests/scheduling/3030-DELETE-attendee-event.result
Normal file
70
testing/tests/scheduling/3030-DELETE-attendee-event.result
Normal 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<
|
||||
|
||||
23
testing/tests/scheduling/3030-DELETE-attendee-event.test
Normal file
23
testing/tests/scheduling/3030-DELETE-attendee-event.test
Normal 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
|
||||
|
||||
21
testing/tests/scheduling/3031-DELETE-reply.test
Normal file
21
testing/tests/scheduling/3031-DELETE-reply.test
Normal 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
|
||||
|
||||
73
testing/tests/scheduling/3032-PUT-iCal-with-attendees.test
Normal file
73
testing/tests/scheduling/3032-PUT-iCal-with-attendees.test
Normal 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
|
||||
|
||||
31
testing/tests/scheduling/3033-DELETE-organizer-event.test
Normal file
31
testing/tests/scheduling/3033-DELETE-organizer-event.test
Normal 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user