diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -2996,6 +2996,13 @@ // set status=CANCELLED on CANCEL messages if ($event['_method'] == 'CANCEL') $event['status'] = 'CANCELLED'; + + // update attachments list, allow attachments update only on REQUEST (#5342) + if ($event['_method'] == 'REQUEST') + $event['deleted_attachments'] = true; + else + unset($event['attachments']); + // show me as free when declined (#1670) if ($status == 'declined' || $event['status'] == 'CANCELLED' || $event_attendee['role'] == 'NON-PARTICIPANT') $event['free_busy'] = 'free'; diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -1972,46 +1972,7 @@ */ public static function from_rcube_event($event, $old = array()) { - // in kolab_storage attachments are indexed by content-id - if (is_array($event['attachments']) || !empty($event['deleted_attachments'])) { - $event['_attachments'] = array(); - - foreach ($event['attachments'] as $attachment) { - $key = null; - // Roundcube ID has nothing to do with the storage ID, remove it - if ($attachment['content'] || $attachment['path']) { - unset($attachment['id']); - } - else { - foreach ((array)$old['_attachments'] as $cid => $oldatt) { - if ($attachment['id'] == $oldatt['id']) - $key = $cid; - } - } - - // flagged for deletion => set to false - if ($attachment['_deleted'] || in_array($attachment['id'], (array)$event['deleted_attachments'])) { - $event['_attachments'][$key] = false; - } - // replace existing entry - else if ($key) { - $event['_attachments'][$key] = $attachment; - } - // append as new attachment - else { - $event['_attachments'][] = $attachment; - } - } - - $event['_attachments'] = array_merge((array)$old['_attachments'], $event['_attachments']); - - // attachments flagged for deletion => set to false - foreach ($event['_attachments'] as $key => $attachment) { - if ($attachment['_deleted'] || in_array($attachment['id'], (array)$event['deleted_attachments'])) { - $event['_attachments'][$key] = false; - } - } - } + kolab_format::merge_attachments($event, $old); return $event; } diff --git a/plugins/libkolab/lib/kolab_format.php b/plugins/libkolab/lib/kolab_format.php --- a/plugins/libkolab/lib/kolab_format.php +++ b/plugins/libkolab/lib/kolab_format.php @@ -704,4 +704,68 @@ $this->obj->setAttachments($vattach); } } + + /** + * Unified way of updating/deleting attachments of edited object + * + * @param array $object Kolab object data + * @param array $old Old version of Kolab object + */ + public static function merge_attachments(&$object, $old) + { + $object['_attachments'] = (array) $old['_attachments']; + + // delete existing attachment(s) + if (!empty($object['deleted_attachments'])) { + foreach ($object['_attachments'] as $idx => $att) { + if ($object['deleted_attachments'] === true || in_array($att['id'], $object['deleted_attachments'])) { + $object['_attachments'][$idx] = false; + } + } + } + + // in kolab_storage attachments are indexed by content-id + foreach ((array) $object['attachments'] as $attachment) { + $key = null; + + // Roundcube ID has nothing to do with the storage ID, remove it + // for uploaded/new attachments + // FIXME: Roundcube uses 'data', kolab_format uses 'content' + if ($attachment['content'] || $attachment['path'] || $attachment['data']) { + unset($attachment['id']); + } + + if ($attachment['id']) { + foreach ((array) $object['_attachments'] as $cid => $att) { + if ($att && $attachment['id'] == $att['id']) { + $key = $cid; + } + } + } + else { + // find attachment by name, so we can update it if exists + // and make sure there are no duplicates + foreach ((array) $object['_attachments'] as $cid => $att) { + if ($att && $attachment['name'] == $att['name']) { + $key = $cid; + } + } + } + + if ($key && $attachment['_deleted']) { + $object['_attachments'][$key] = false; + } + // replace existing entry + else if ($key) { + $object['_attachments'][$key] = $attachment; + } + // append as new attachment + else { + $object['_attachments'][] = $attachment; + } + } + + unset($object['attachments']); + unset($object['deleted_attachments']); + } } diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php --- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php +++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php @@ -1308,46 +1308,8 @@ $object['recurrence'] = $old['recurrence']; } - // delete existing attachment(s) - if (!empty($task['deleted_attachments'])) { - foreach ($task['deleted_attachments'] as $attachment) { - if (is_array($object['_attachments'])) { - foreach ($object['_attachments'] as $idx => $att) { - if ($att['id'] == $attachment) - $object['_attachments'][$idx] = false; - } - } - } - unset($task['deleted_attachments']); - } - - // in kolab_storage attachments are indexed by content-id - if (is_array($task['attachments'])) { - foreach ($task['attachments'] as $idx => $attachment) { - $key = null; - // Roundcube ID has nothing to do with the storage ID, remove it - if ($attachment['content'] || $attachment['path']) { - unset($attachment['id']); - } - else { - foreach ((array)$old['_attachments'] as $cid => $oldatt) { - if ($oldatt && $attachment['id'] == $oldatt['id']) - $key = $cid; - } - } - - // replace existing entry - if ($key) { - $object['_attachments'][$key] = $attachment; - } - // append as new attachment - else { - $object['_attachments'][] = $attachment; - } - } - - unset($object['attachments']); - } + unset($task['attachments']); + kolab_format::merge_attachments($object, $old); // allow sequence increments if I'm the organizer if ($this->plugin->is_organizer($object) && empty($object['_method'])) { diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php --- a/plugins/tasklist/tasklist.php +++ b/plugins/tasklist/tasklist.php @@ -2088,6 +2088,15 @@ if ($task['_method'] == 'CANCEL') { $task['status'] = 'CANCELLED'; } + + // update attachments list, allow attachments update only on REQUEST (#5342) + if ($task['_method'] == 'REQUEST') { + $task['deleted_attachments'] = true; + } + else { + unset($task['attachments']); + } + // show me as free when declined (#1670) if ($status == 'declined' || $task['status'] == 'CANCELLED') { $task['free_busy'] = 'free';