diff --git a/lib/kolab_sync_data_calendar.php b/lib/kolab_sync_data_calendar.php --- a/lib/kolab_sync_data_calendar.php +++ b/lib/kolab_sync_data_calendar.php @@ -803,21 +803,88 @@ throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::INVALID_REQUEST); } - if ($event['free_busy']) { - $old['free_busy'] = $event['free_busy']; + $canceled = ($event['_method'] ?? '') == 'CANCEL' || ($event['status'] ?? '') == 'CANCELLED'; + + // A single recurring event occurrence + if (!empty($event['recurrence_date'])) { + $event['recurrence'] = []; + + // A cancellation (it should not happen) + if ($canceled) { + // remove the matching RDATE entry + if (!empty($old['recurrence']['RDATE'])) { + foreach ($old['recurrence']['RDATE'] as $j => $rdate) { + if ($rdate->format('Ymd') == $event['recurrence_date']->format('Ymd')) { + unset($old['recurrence']['RDATE'][$j]); + break; + } + } + } + + // add exception to master event + $old['recurrence']['EXDATE'][] = $event['recurrence_date']; + } + else { + if ($status) { + $this->update_attendee_status($event, $status); + $status = null; + } + + if (!isset($old['exceptions'])) { + if (isset($old['recurrence']['EXCEPTIONS'])) { + $old['exceptions'] = &$old['recurrence']['EXCEPTIONS']; + } + else { + $old['exceptions'] = []; + } + } + + $existing = false; + foreach ($old['exceptions'] as $i => $exception) { + if (libcalendaring::is_recurrence_exception($event, $exception)) { + $old['exceptions'][$i] = $event; + $existing = true; + } + } + + if (!$existing) { + $old['exceptions'][] = $event; + } + } + } + // User accepted an event cancellation (it should not happen) + else if ($canceled) { + // FIXME: What should happen when user declined a cancellation? + $old['status'] = 'CANCELLED'; + // FIXME: Should we still update the attendee status at all? Maybe force CANCELLED? + $status = null; + // FIXME: We probably should just delete the event. Some clients do not handle cancelled status + // properly (maybe it's our fault?). + } + // A main event update + else if (isset($event['sequence']) && $event['sequence'] > $old['sequence']) { + // FIXME: Can we be smarter here? Should we update everything? What about e.g. new attendees? + // And do we need to check the sequence? + $props = ['start', 'end', 'title', 'description', 'location', 'free_busy']; + + foreach ($props as $prop) { + if (isset($event[$prop])) { + $old[$prop] = $event[$prop]; + } + } + + // Copy new custom properties + if (!empty($event['x-custom'])) { + foreach ($event['x-custom'] as $key => $val) { + $old['x-custom'][$key] = $val; + } + } } // Updating an existing event is most-likely a response // to an iTip request with bumped SEQUENCE $old['sequence'] += 1; - // Copy new custom properties - if (!empty($event['x-custom'])) { - foreach ($event['x-custom'] as $key => $val) { - $old['x-custom'][$key] = $val; - } - } - // Update the event return $this->save_event($old, $status); } diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php --- a/lib/kolab_sync_data_email.php +++ b/lib/kolab_sync_data_email.php @@ -458,6 +458,10 @@ $meeting['responseRequested'] = 1; } else { $meeting['meetingMessageType'] = Syncroton_Model_EmailMeetingRequest::MESSAGE_TYPE_NORMAL; + + if ($event['_method'] == 'CANCEL') { + $result['messageClass'] = 'IPM.Schedule.Meeting.Canceled'; + } } // New time proposals aren't supported by Kolab.