Improved handling of event modifications:

- only some event attributes modified by the organizer get also modified in attendees' instances of the event,
- revoked invitations mark the according attendee's event appropriately,
- a changed event time resets all attendees' PARTSTAT to NEEDS-ACTION.
This commit is contained in:
Frank Steinberg 2016-01-21 11:37:07 +01:00 committed by Florian Schlichting
parent 4e5d3ceda4
commit 9363a3d19a
4 changed files with 169 additions and 61 deletions

View File

@ -622,6 +622,7 @@ function do_scheduling_requests( vCalendar $resource, $create, $old_data = null
if ( !$qry->Exec('PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
dbg_error_log( 'PUT', "Could not find event in attendee's calendars" );
$attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
unset($row); // signal "not found" below
} else {
$row = $qry->Fetch();
$r = new DAVResource($row);
@ -639,13 +640,40 @@ function do_scheduling_requests( vCalendar $resource, $create, $old_data = null
$response = '5.2'; // No scheduling support for user
}
else {
if ($attendee_is_new || !$row) {
$this_schedule_request = clone($schedule_request);
$this_resource = clone($resource);
} else {
dbg_error_log('PUT',"adjusting only some major properties in %s's instance of the event", $schedule_target->username());
$this_resource = new vCalendar($row->caldav_data);
$these_events = $this_resource->GetComponents("VEVENT");
$this_event = $these_events[0];
$events = $resource->GetComponents("VEVENT");
$event = $events[0];
// ??? CLASS, CREATED, LAST-MODIFIED, ATTENDEE, ORGANIZER, others???
$this_event->SetProperties( $event->GetProperties('DTSTAMP'), 'DTSTAMP' );
$this_event->SetProperties( $event->GetProperties('SEQUENCE'), 'SEQUENCE' );
$this_event->SetProperties( $event->GetProperties('DTSTART'), 'DTSTART' );
$this_event->SetProperties( $event->GetProperties('DTEND'), 'DTEND' );
$this_event->SetProperties( $event->GetProperties('DURATION'), 'DURATION' );
$this_event->SetProperties( $event->GetProperties('SUMMARY'), 'SUMMARY' );
$this_event->SetProperties( $event->GetProperties('LOCATION'), 'LOCATION' );
$this_event->SetProperties( $event->GetProperties('DESCRIPTION'), 'DESCRIPTION' );
$this_event->SetProperties( $event->GetProperties('GEO'), 'GEO' );
$this_event->SetProperties( $event->GetProperties('RESOURCES'), 'RESOURCES' );
$this_event->SetProperties( $event->GetProperties('STATUS'), 'STATUS' );
$this_event->SetProperties( $event->GetProperties('ATTENDEE'), 'ATTENDEE' );
$this_resource->SetComponents($these_events, "VEVENT");
$this_schedule_request = clone($this_resource);
$this_schedule_request->AddProperty('METHOD', 'REQUEST');
}
$attendee_inbox = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-inbox')));
if ( ! $attendee_inbox->HavePrivilegeTo('schedule-deliver-invite') ) {
$response = '3.8'; // No authority to deliver invitations to user.
}
else if ( $attendee_inbox->WriteCalendarMember($schedule_request, $attendee_is_new) !== false ) {
else if ( $attendee_inbox->WriteCalendarMember($this_schedule_request, $attendee_is_new) !== false ) {
$response = '1.2'; // Scheduling invitation delivered successfully
if ( $attendee_calendar->WriteCalendarMember($orig_resource, $attendee_is_new) === false ) {
if ( $attendee_calendar->WriteCalendarMember($this_resource, $attendee_is_new) === false ) {
dbg_error_log('ERROR','Could not write %s calendar member to %s', ($attendee_is_new?'new':'updated'),
$attendee_calendar->dav_name(), $attendee_calendar->dav_name(), $schedule_target->username());
trace_bug('Failed to write scheduling resource.');
@ -681,9 +709,52 @@ function do_scheduling_requests( vCalendar $resource, $create, $old_data = null
if ( !$create ) {
foreach( $removed_attendees AS $attendee ) {
$email = preg_replace( '/^mailto:/i', '', $attendee->Value() );
$schedule_target = new Principal('email',$email);
if ( $schedule_target->Exists() ) {
$attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
// Instead of always writing to schedule-default-calendar, we first try to
// find a calendar with an existing instance of the event.
$sql = 'SELECT caldav_data.dav_name, caldav_data.caldav_data, caldav_data.collection_id 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';
$qry = new AwlQuery($sql,$schedule_target->user_no(), $uid);
if ( !$qry->Exec('PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
dbg_error_log( 'PUT', "Could not find event in attendee's calendars" );
$attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
} else {
$row = $qry->Fetch();
$r = new DAVResource($row);
$attendee_calendar = new WritableCollection(array('path' => $r->parent_path()));
if ($attendee_calendar->IsCalendar()) {
dbg_error_log( 'PUT', "found the event in attendee's calendar %s", $attendee_calendar->dav_name() );
} else {
dbg_error_log( 'PUT', 'could not find the event in any calendar, using schedule-default-calendar');
$attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
}
dbg_error_log('PUT',"marking %s's instance of the event to show that the user's invitation has been revoked", $schedule_target->username());
$this_resource = new vCalendar($row->caldav_data);
$these_events = $this_resource->GetComponents("VEVENT");
$this_event = $these_events[0];
$properties[] = new vProperty( "DESCRIPTION:Your invitation to this event has been revoked." );
$this_event->SetProperties( $properties, 'DESCRIPTION' );
$properties[] = new vProperty( "STATUS:CANCELLED" );
$this_event->SetProperties( $properties, 'STATUS' );
$this_event->SetProperties( null, 'ATTENDEE' );
$this_resource->SetComponents($these_events, "VEVENT");
$this_schedule_request = clone($this_resource);
$this_schedule_request->AddProperty('METHOD', 'REQUEST');
$attendee_inbox = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-inbox')));
if ( ! $attendee_inbox->HavePrivilegeTo('schedule-deliver-invite') ) {
$response = '3.8'; // No authority to deliver invitations to user.
}
else if ( $attendee_inbox->WriteCalendarMember($this_schedule_request, false) !== false ) {
$response = '1.2'; // Scheduling invitation delivered successfully
if ( $attendee_calendar->WriteCalendarMember($this_resource, false) === false ) {
dbg_error_log('ERROR','Could not write updated calendar member');
trace_bug('Failed to write scheduling resource.');
}
}
}
}
}
}
@ -1325,14 +1396,6 @@ function write_resource( DAVResource $resource, $caldav_data, DAVResource $colle
$qry = new AwlQuery();
$qry->Begin();
$dav_params = array(
':etag' => $etag,
':dav_data' => $caldav_data,
':caldav_type' => $resource_type,
':session_user' => $author,
':weak_etag' => $weak_etag
);
$calitem_params = array(
':etag' => $etag
);
@ -1351,6 +1414,48 @@ function write_resource( DAVResource $resource, $caldav_data, DAVResource $colle
}
$dav_id = $row->dav_id;
$old_dav_data = $row->caldav_data;
// reset all attendees' partstat to needs-action in vcal and caldav_date here, if time of event has been modified.
if ($old_dav_data) {
$modified = false;
$oldvcal = new vCalendar( $old_dav_data );
$oldr = $oldvcal->GetComponents('VTIMEZONE',false);
$oldfirst = $oldr[0];
$dtstart = $first->GetPValue('DTSTART');
$olddtstart = $oldfirst->GetPValue('DTSTART');
if (strcmp($dtstart, $olddtstart)) $modified = true;
$dtend = $first->GetPValue('DTEND');
$olddtend = $oldfirst->GetPValue('DTEND');
if (strcmp($dtend, $olddtend)) $modified = true;
$duration = $first->GetPValue('DURATION');
$oldduration = $oldfirst->GetPValue('DURATION');
$organizer = $vcal->GetOrganizer();
if (strcmp($duration, $oldduration)) $modified = true;
if (($modified == true) && !($organizer === false || empty($organizer))) {
dbg_error_log( 'PUT', "event time attributes have been modified, reset all attendees' PARTSTAT to NEEDS-ACTION.");
$organizer_email = preg_replace( '/^mailto:/i', '', $organizer->Value() );
$attendees = $first->GetProperties('ATTENDEE');
foreach( $attendees AS $attendee ) {
$attendee_email = preg_replace( '/^mailto:/', '', $attendee->Value() );
if ( $attendee_email != $organizer_email ) {
$attendee->SetParameterValue("PARTSTAT", "NEEDS-ACTION");
$attendee->SetParameterValue("SCHEDULE-STATUS", "1.0");
}
}
$first->SetProperties($attendees,'ATTENDEE');
$caldav_data = $vcal->Render(null, true);
dbg_error_log( 'PUT', "event time attributes have been modified, reset all attendees' PARTSTAT completed.");
}
}
$dav_params = array(
':etag' => $etag,
':dav_data' => $caldav_data,
':caldav_type' => $resource_type,
':session_user' => $author,
':weak_etag' => $weak_etag
);
$dav_params[':dav_id'] = $dav_id;
$calitem_params[':dav_id'] = $dav_id;

View File

@ -289,7 +289,8 @@ ORGANIZER;CN=User 1:MAILTO:user1@example.net
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RS
VP=TRUE;CN=User 1;LANGUAGE=en:MAILTO:user1@example.net
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RS
VP=TRUE;CN=User 2;LANGUAGE=en:MAILTO:user2@example.net
VP=TRUE;CN=User 2;LANGUAGE=en;SCHEDULE-STATUS=1.2:MAILTO:user2@example.n
et
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RS
VP=TRUE;CN=User 3;LANGUAGE=en:MAILTO:user3@example.net
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RS
@ -404,9 +405,11 @@ ORGANIZER;CN=User 1:MAILTO:user1@example.net
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RS
VP=TRUE;CN=User 1;LANGUAGE=en:MAILTO:user1@example.net
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RS
VP=TRUE;CN=User 2;LANGUAGE=en:MAILTO:user2@example.net
VP=TRUE;CN=User 2;LANGUAGE=en;SCHEDULE-STATUS=1.2:MAILTO:user2@example.n
et
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RS
VP=TRUE;CN=User 3;LANGUAGE=en:MAILTO:user3@example.net
VP=TRUE;CN=User 3;LANGUAGE=en;SCHEDULE-STATUS=1.2:MAILTO:user3@example.n
et
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RS
VP=TRUE;CN=User 4;LANGUAGE=en:MAILTO:user4@example.net
END:VEVENT

View File

@ -35,17 +35,17 @@ BEGIN:VEVENT
CREATED:20111018T195845Z
UID:E1A13F04-iCal-schedule
DTEND;TZID=Pacific/Auckland:20111019T140000
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
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
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=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;SCHEDULE-STATUS=1.2
:mailto:user1@example.net
END:VEVENT
END:VCALENDAR
<
@ -79,18 +79,18 @@ 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
TRANSP:OPAQUE
SUMMARY:Meeting with User1
DTSTART;TZID=Pacific/Auckland:20111019T130000
ORGANIZER;CN=Manager 1;SCHEDULE-STATUS=1.2:mailto:manager1@example.net
DTSTAMP:20111024T035702Z
ORGANIZER;CN="Manager 1":mailto:manager1@example.net
SEQUENCE:6
DTSTART;TZID=Pacific/Auckland:20111019T130000
DTEND;TZID=Pacific/Auckland:20111019T140000
SUMMARY:Meeting with User1
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
ATTENDEE;CN=user1@example.net;CUTYPE=INDIVIDUAL;EMAIL=user1@example.net;
PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;SCHEDULE-STATUS=1.0
:mailto:user1@example.net
END:VEVENT
END:VCALENDAR
<
@ -170,18 +170,18 @@ 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
TRANSP:OPAQUE
SUMMARY:Meeting with User1
DTSTART;TZID=Pacific/Auckland:20111019T130000
ORGANIZER;CN=Manager 1;SCHEDULE-STATUS=1.2:mailto:manager1@example.net
DTSTAMP:20111024T035702Z
ORGANIZER;CN="Manager 1":mailto:manager1@example.net
SEQUENCE:6
DTSTART;TZID=Pacific/Auckland:20111019T130000
DTEND;TZID=Pacific/Auckland:20111019T140000
SUMMARY:Meeting with User1
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
ATTENDEE;CN=user1@example.net;CUTYPE=INDIVIDUAL;EMAIL=user1@example.net;
PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;SCHEDULE-STATUS=1.0
:mailto:user1@example.net
END:VEVENT
END:VCALENDAR
<

View File

@ -243,22 +243,22 @@ 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
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
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
ATTENDEE;CN=user2@example.net;CUTYPE=INDIVIDUAL;EMAIL=user2@example.net;
PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user2@exampl
e.net
ATTENDEE;CN=user3@example.net;CUTYPE=INDIVIDUAL;EMAIL=user3@example.net;
PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user3@exampl
e.net
TRANSP:OPAQUE
SUMMARY:Meeting with User1
DTSTART;TZID=Pacific/Auckland:20111019T130000
DTSTAMP:20111024T035702Z
ORGANIZER;CN="Manager 1":mailto:manager1@example.net
ORGANIZER;CN=Manager 1:mailto:manager1@example.net
SEQUENCE:7
END:VEVENT
END:VCALENDAR
@ -346,22 +346,22 @@ 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
ATTENDEE;CN=Manager 1;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:
mailto:manager1@example.net
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
ATTENDEE;CN=user2@example.net;CUTYPE=INDIVIDUAL;EMAIL=user2@example.net;
PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;SCHEDULE-STATUS=1.2
:mailto:user2@example.net
ATTENDEE;CN=user3@example.net;CUTYPE=INDIVIDUAL;EMAIL=user3@example.net;
PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:user3@exampl
e.net
TRANSP:OPAQUE
SUMMARY:Meeting with User1
DTSTART;TZID=Pacific/Auckland:20111019T130000
DTSTAMP:20111024T035702Z
ORGANIZER;CN="Manager 1":mailto:manager1@example.net
ORGANIZER;CN=Manager 1:mailto:manager1@example.net
SEQUENCE:7
END:VEVENT
END:VCALENDAR