Branch 'dev/recurring-invitations' - 2 commits - plugins/calendar plugins/libcalendaring plugins/libkolab

Thomas Brüderli bruederli at kolabsys.com
Fri Feb 20 00:11:46 CET 2015


 plugins/calendar/calendar.php                      |  105 ++++++++-----
 plugins/calendar/calendar_ui.js                    |   12 +
 plugins/calendar/drivers/calendar_driver.php       |    3 
 plugins/calendar/drivers/kolab/kolab_calendar.php  |   34 ----
 plugins/calendar/drivers/kolab/kolab_driver.php    |  165 +++++++++++++++++++--
 plugins/libcalendaring/lib/libcalendaring_itip.php |    7 
 plugins/libcalendaring/libcalendaring.js           |    2 
 plugins/libkolab/lib/kolab_storage_folder.php      |    7 
 8 files changed, 247 insertions(+), 88 deletions(-)

New commits:
commit 02ef2e60505a6319599042c9e6e92a7a2f42de8c
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Fri Feb 20 00:11:40 2015 +0100

    Split recurring event into a new series when modifying with this-and-future option (#4386); optimize copying of attachments into new event

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index f5c2c04..8be5956 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -865,6 +865,7 @@ class calendar extends rcube_plugin
           $event['id'] = $event['uid'];
           $event['_savemode'] = 'all';
           $this->cleanup_event($event);
+          $this->event_save_success($event, null, $action, true);
         }
         $reload = $success && $event['recurrence'] ? 2 : 1;
         break;
@@ -873,21 +874,15 @@ class calendar extends rcube_plugin
         $this->write_preprocess($event, $action);
         if ($success = $this->driver->edit_event($event)) {
           $this->cleanup_event($event);
-          if ($success !== true) {
-            $event['id'] = $success;
-            $old = null;
-          }
+          $this->event_save_success($event, $old, $action, $success);
         }
-        $reload =  $success && ($event['recurrence'] || $event['_savemode'] || $event['_fromcalendar']) ? 2 : 1;
+        $reload = $success && ($event['recurrence'] || $event['_savemode'] || $event['_fromcalendar']) ? 2 : 1;
         break;
       
       case "resize":
         $this->write_preprocess($event, $action);
         if ($success = $this->driver->resize_event($event)) {
-          if ($success !== true) {
-            $event['id'] = $success;
-            $old = null;
-          }
+          $this->event_save_success($event, $old, $action, $success);
         }
         $reload = $event['_savemode'] ? 2 : 1;
         break;
@@ -895,10 +890,7 @@ class calendar extends rcube_plugin
       case "move":
         $this->write_preprocess($event, $action);
         if ($success = $this->driver->move_event($event)) {
-          if ($success !== true) {
-            $event['id'] = $success;
-            $old = null;
-          }
+          $this->event_save_success($event, $old, $action, $success);
         }
         $reload  = $success && $event['_savemode'] ? 2 : 1;
         break;
@@ -958,6 +950,9 @@ class calendar extends rcube_plugin
           else
             $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error');
         }
+        else if ($success) {
+          $this->event_save_success($event, $old, $action, $success);
+        }
         break;
 
       case "undo":
@@ -1000,8 +995,8 @@ class calendar extends rcube_plugin
         $event = $ev;
 
         // compose a list of attendees affected by this change
-        $updated_attendees = array_filter(array_map(function($j) use ($ev) {
-          return $ev['attendees'][$j];
+        $updated_attendees = array_filter(array_map(function($j) use ($event) {
+          return $event['attendees'][$j];
         }, $attendees));
 
         if ($success = $this->driver->edit_rsvp($event, $status, $updated_attendees)) {
@@ -1147,24 +1142,63 @@ class calendar extends rcube_plugin
         $this->rc->output->show_message('calendar.errorsaving', 'error');
     }
 
-    // send out notifications
-    if ($success && $event['_notify'] && ($event['attendees'] || $old['attendees'])) {
-      $_savemode = $event['_savemode'];
+    // unlock client
+    $this->rc->output->command('plugin.unlock_saving');
 
-      // make sure we have the complete record
-      $event = $action == 'remove' ? $old : $this->driver->get_event($event);
-      $event['_savemode'] = $_savemode;
+    // update event object on the client or trigger a complete refretch if too complicated
+    if ($reload) {
+      $args = array('source' => $event['calendar']);
+      if ($reload > 1)
+        $args['refetch'] = true;
+      else if ($success && $action != 'remove')
+        $args['update'] = $this->_client_event($this->driver->get_event($event), true);
+      $this->rc->output->command('plugin.refresh_calendar', $args);
+    }
+  }
 
-      if ($old) {
-        $old['thisandfuture'] = $_savemode == 'future';
+  /**
+   * Helper method sending iTip notifications after successful event updates
+   */
+  private function event_save_success(&$event, $old, $action, $success)
+  {
+    // $success is a new event ID
+    if ($success !== true) {
+      // send update notification on the main event
+      if ($event['_savemode'] == 'future' && $event['_notify'] && $old['attendees'] && $old['recurrence_id']) {
+        $master = $this->driver->get_event(array('id' => $old['recurrence_id'], 'calendar' => $old['calendar']));
+        unset($master['_instance'], $master['recurrence_date']);
+
+        $sent = $this->notify_attendees($master, null, $action, $event['_comment']);
+        if ($sent < 0)
+          $this->rc->output->show_message('calendar.errornotifying', 'error');
       }
 
+      $event['id'] = $success;
+      $event['_savemode'] = 'all';
+      $event['attendees'] = $master['attendees'];  // this tricks us into the next if clause
+      $old = null;
+    }
+
+    // send out notifications
+    if ($event['_notify'] && ($event['attendees'] || $old['attendees'])) {
+      $_savemode = $event['_savemode'];
+
       // send notification for the main event when savemode is 'all'
-      if ($_savemode == 'all' && $event['recurrence_id']) {
-        $event['id'] = $event['recurrence_id'];
+      if ($action != 'remove' && $_savemode == 'all' && $old['recurrence_id']) {
+        $event['id'] = $old['recurrence_id'];
         $event = $this->driver->get_event($event);
         unset($event['_instance'], $event['recurrence_date']);
       }
+      else {
+        // make sure we have the complete record
+        $event = $action == 'remove' ? $old : $this->driver->get_event($event);
+      }
+
+      $event['_savemode'] = $_savemode;
+
+      if ($old) {
+        $old['thisandfuture'] = $_savemode == 'future';
+      }
 
       // only notify if data really changed (TODO: do diff check on client already)
       if (!$old || $action == 'remove' || self::event_diff($event, $old)) {
@@ -1175,19 +1209,6 @@ class calendar extends rcube_plugin
           $this->rc->output->show_message('calendar.errornotifying', 'error');
       }
     }
-
-    // unlock client
-    $this->rc->output->command('plugin.unlock_saving');
-
-    // update event object on the client or trigger a complete refretch if too complicated
-    if ($reload) {
-      $args = array('source' => $event['calendar']);
-      if ($reload > 1)
-        $args['refetch'] = true;
-      else if ($success && $action != 'remove')
-        $args['update'] = $this->_client_event($this->driver->get_event($event), true);
-      $this->rc->output->command('plugin.refresh_calendar', $args);
-    }
   }
 
   /**
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index e7f55cd..5a26c18 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -2432,6 +2432,10 @@ function rcube_calendar_ui(settings)
 
             attendees.push(i)
           }
+          else if (response != 'DELEGATED' && data['delegated-from'] &&
+              settings.identity.emails.indexOf(';'+String(data['delegated-from']).toLowerCase()) >= 0) {
+            delete data['delegated-from'];
+          }
 
           // set free_busy status to transparent if declined (#4425)
           if (data.status == 'DECLINED' || data.role == 'NON-PARTICIPANT') {
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index a392a9f..64bb082 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -639,6 +639,7 @@ class kolab_calendar extends kolab_storage_folder_api
       if (($next_event['start'] <= $end && $next_event['end'] >= $start) || ($event_id && $rec_id == $event_id)) {
         $rec_event = $this->_to_rcube_event($next_event);
         $rec_event['_instance'] = $instance_id;
+        $rec_event['_count'] = $i + 1;
 
         if ($overlay_data || $exdata[$datestr])  // copy data from exception
           kolab_driver::merge_exception_data($rec_event, $exdata[$datestr] ?: $overlay_data);
@@ -687,38 +688,7 @@ class kolab_calendar extends kolab_storage_folder_api
    */
   private function _from_rcube_event($event, $old = array())
   {
-    // in kolab_storage attachments are indexed by content-id
-    $event['_attachments'] = array();
-    if (is_array($event['attachments'])) {
-      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']) {
-          $event['_attachments'][$key] = false;
-        }
-        // replace existing entry
-        else if ($key) {
-          $event['_attachments'][$key] = $attachment;
-        }
-        // append as new attachment
-        else {
-          $event['_attachments'][] = $attachment;
-        }
-      }
-
-      unset($event['attachments']);
-    }
+    $event = kolab_driver::from_rcube_event($event, $old);
 
     // set current user as ORGANIZER
     $identity = $this->cal->rc->user->list_emails(true);
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 48dbafd..d6fb55a 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -621,7 +621,19 @@ class kolab_driver extends calendar_driver
    */
   public function edit_rsvp(&$event, $status, $attendees)
   {
-    if (($ret = $this->update_attendees($event, $attendees)) && $this->rc->config->get('kolab_invitation_calendars')) {
+    $update_event = $event;
+
+    // apply changes to master (and all exceptions)
+    if ($event['_savemode'] == 'all' && $event['recurrence_id']) {
+      if ($storage = $this->get_calendar($event['calendar'])) {
+        $update_event = $storage->get_event($event['recurrence_id']);
+        $update_event['_savemode'] = $event['_savemode'];
+        unset($update_event['recurrence_id']);
+        self::merge_attendee_data($update_event, $attendees);
+      }
+    }
+
+    if (($ret = $this->update_attendees($update_event, $attendees)) && $this->rc->config->get('kolab_invitation_calendars')) {
       // re-assign to the according (virtual) calendar
       if (strtoupper($status) == 'DECLINED')
         $event['calendar'] = self::INVITATIONS_CALENDAR_DECLINED;
@@ -687,6 +699,7 @@ class kolab_driver extends calendar_driver
   {
     if (($storage = $this->get_calendar($event['calendar'])) && ($ev = $storage->get_event($event['id']))) {
       unset($ev['sequence']);
+      self::clear_attandee_noreply($ev);
       return $this->update_event($event + $ev);
     }
 
@@ -703,6 +716,7 @@ class kolab_driver extends calendar_driver
   {
     if (($storage = $this->get_calendar($event['calendar'])) && ($ev = $storage->get_event($event['id']))) {
       unset($ev['sequence']);
+      self::clear_attandee_noreply($ev);
       return $this->update_event($event + $ev);
     }
 
@@ -928,6 +942,7 @@ class kolab_driver extends calendar_driver
     if ($old['recurrence'] || $old['recurrence_id']) {
       $master = $old['recurrence_id'] ? $fromcalendar->get_event($old['recurrence_id']) : $old;
       $savemode = $event['_savemode'] ?: ($old['recurrence_id'] ? 'current' : 'all');
+      $object = $fromcalendar->storage->get_object($master['uid']);
 
       // this-and-future on the first instance equals to 'all'
       if (!$old['recurrence_id'] && $savemode == 'future')
@@ -953,20 +968,70 @@ class kolab_driver extends calendar_driver
       case 'new':
         // save submitted data as new (non-recurring) event
         $event['recurrence'] = array();
+        $event['_copyfrom'] = $object['_msguid'];
+        $event['_mailbox'] = $object['_mailbox'];
         $event['uid'] = $this->cal->generate_uid();
-        unset($event['recurrence_id'], $event['_instance'], $event['id']);
+        unset($event['recurrence_id'], $event['recurrence_date'], $event['_instance'], $event['id']);
 
-        // copy attachment data to new event
-        foreach ((array)$event['attachments'] as $idx => $attachment) {
-          if (!$attachment['content'])
-            $event['attachments'][$idx]['content'] = $this->get_attachment_body($attachment['id'], $master);
-        }
+        // copy attachment metadata to new event
+        $event = self::from_rcube_event($event, $object);
 
+        self::clear_attandee_noreply($event);
         if ($success = $storage->insert_event($event))
           $success = $event['uid'];
         break;
 
       case 'future':
+        // create a new recurring event
+        $event['_copyfrom'] = $object['_msguid'];
+        $event['_mailbox'] = $object['_mailbox'];
+        $event['uid'] = $this->cal->generate_uid();
+        unset($event['recurrence_id'], $event['recurrence_date'], $event['_instance'], $event['id']);
+
+        // copy attachment metadata to new event
+        $event = self::from_rcube_event($event, $object);
+  
+        // remove recurrence exceptions on re-scheduling
+        if ($reschedule) {
+          unset($event['recurrence']['EXCEPTIONS']);
+        }
+        else if (is_array($event['recurrence']['EXCEPTIONS'])) {
+          // only keep relevant exceptions
+          $event['recurrence']['EXCEPTIONS'] = array_filter($event['recurrence']['EXCEPTIONS'], function($exception) use ($event) {
+            return $exception['start'] > $event['start'];
+          });
+        }
+
+        // compute remaining occurrences
+        if ($event['recurrence']['COUNT']) {
+          if (!$old['_count'])
+            $old['_count'] = $this->get_recurrence_count($object, $event['start']);
+          $event['recurrence']['COUNT'] -= intval($old['_count']);
+        }
+
+        // set until-date on master event
+        $master['recurrence']['UNTIL'] = clone $event['start'];
+        $master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
+        unset($master['recurrence']['COUNT']);
+
+        // remove all exceptions after $event['start']
+        if (is_array($master['recurrence']['EXCEPTIONS'])) {
+          $master['recurrence']['EXCEPTIONS'] = array_filter($master['recurrence']['EXCEPTIONS'], function($exception) use ($event) {
+            return $exception['start'] < $event['start'];
+          });
+        }
+
+        // save new event
+        if ($success = $storage->insert_event($event)) {
+          $success = $event['uid'];
+
+          // update master event (no rescheduling!)
+          $master['sequence'] = $object['sequence'];
+          self::clear_attandee_noreply($master);
+          $udated = $storage->update_event($master);
+        }
+        break;
+
       case 'current':
         // recurring instances shall not store recurrence rules and attachments
         $event['recurrence'] = array();
@@ -1215,6 +1280,17 @@ class kolab_driver extends calendar_driver
   }
 
   /**
+   * Remove the noreply flags from attendees
+   */
+  public static function clear_attandee_noreply(&$event)
+  {
+    foreach ((array)$event['attendees'] as $i => $attendee) {
+      unset($event['attendees'][$i]['noreply']);
+    }
+  }
+
+
+  /**
    * Merge certain properties from the overlay event to the base event object
    *
    * @param array The event object to be altered
@@ -1571,6 +1647,29 @@ class kolab_driver extends calendar_driver
   }
 
   /**
+   *
+   */
+  private function get_recurrence_count($event, $dtstart)
+  {
+    // use libkolab to compute recurring events
+    if (class_exists('kolabcalendaring') && $object['_formatobj']) {
+        $recurrence = new kolab_date_recurrence($object['_formatobj']);
+    }
+    else {
+      // fallback to local recurrence implementation
+      require_once($this->cal->home . '/lib/calendar_recurrence.php');
+      $recurrence = new calendar_recurrence($this->cal, $event);
+    }
+
+    $count = 0;
+    while (($next_event = $recurrence->next_instance()) && $next_event['start'] <= $dtstart && $count < 1000) {
+      $count++;
+    }
+
+    return $count;
+  }
+
+  /**
    * Fetch free/busy information from a person within the given range
    */
   public function get_freebusy_list($email, $start, $end)
@@ -1749,6 +1848,13 @@ class kolab_driver extends calendar_driver
       $record['_instance'] = $record['start']->format($recurrence_id_format);
     }
 
+    // clean up exception data
+    if (is_array($record['recurrence']['EXCEPTIONS'])) {
+      array_walk($record['recurrence']['EXCEPTIONS'], function(&$exception) {
+        unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj'], $exception['_attachments']);
+      });
+    }
+
     // remove internals
     unset($record['_mailbox'], $record['_msguid'], $record['_formatobj'], $record['_attachments'], $record['x-custom']);
 
@@ -1756,6 +1862,49 @@ class kolab_driver extends calendar_driver
   }
 
   /**
+   *
+   */
+  public static function from_rcube_event($event, $old = array())
+  {
+    // in kolab_storage attachments are indexed by content-id
+    if (is_array($event['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']) {
+          $event['_attachments'][$key] = false;
+        }
+        // replace existing entry
+        else if ($key) {
+          $event['_attachments'][$key] = $attachment;
+        }
+        // append as new attachment
+        else {
+          $event['_attachments'][] = $attachment;
+        }
+      }
+
+      unset($event['attachments']);
+    }
+
+    return $event;
+  }
+
+
+  /**
    * Set CSS class according to the event's attendde partstat
    */
   public static function add_partstat_class($event, $partstats, $user = null)
diff --git a/plugins/libcalendaring/lib/libcalendaring_itip.php b/plugins/libcalendaring/lib/libcalendaring_itip.php
index 41148c6..14dacf4 100644
--- a/plugins/libcalendaring/lib/libcalendaring_itip.php
+++ b/plugins/libcalendaring/lib/libcalendaring_itip.php
@@ -250,7 +250,7 @@ class libcalendaring_itip
         // set RSVP for every attendee
         else if ($method == 'REQUEST') {
             foreach ($event['attendees'] as $i => $attendee) {
-                if ($attendee['status'] != 'DELEGATED' && !isset($attendee['rsvp'])) {
+                if ($attendee['status'] != 'DELEGATED') {
                     $event['attendees'][$i]['rsvp']= (bool)$rsvp;
                 }
             }
diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js
index 71c101d..1c8fe16 100644
--- a/plugins/libcalendaring/libcalendaring.js
+++ b/plugins/libcalendaring/libcalendaring.js
@@ -965,7 +965,7 @@ rcube_libcalendaring.itip_rsvp_recurring = function(btn, callback)
 {
     var mnu = $('<ul></ul>').addClass('popupmenu libcal-rsvp-replymode');
 
-    $.each(['all','current','future'], function(i, mode) {
+    $.each(['all','current'/*,'future'*/], function(i, mode) {
         $('<li><a>' + rcmail.get_label('rsvpmode'+mode, 'libcalendaring') + '</a>')
         .addClass('ui-menu-item')
         .attr('rel', mode)
diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index ab3c63f..e0bf52e 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -616,7 +616,8 @@ class kolab_storage_folder extends kolab_storage_folder_api
             $type = $this->type;
 
         // copy attachments from old message
-        if (!empty($object['_msguid']) && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox']))) {
+        $copyfrom = $object['_copyfrom'] ?: $object['_msguid'];
+        if (!empty($copyfrom) && ($old = $this->cache->get($copyfrom, $type, $object['_mailbox']))) {
             foreach ((array)$old['_attachments'] as $key => $att) {
                 if (!isset($object['_attachments'][$key])) {
                     $object['_attachments'][$key] = $old['_attachments'][$key];
@@ -628,7 +629,7 @@ class kolab_storage_folder extends kolab_storage_folder_api
                 // load photo.attachment from old Kolab2 format to be directly embedded in xcard block
                 else if ($type == 'contact' && ($key == 'photo.attachment' || $key == 'kolab-picture.png') && $att['id']) {
                     if (!isset($object['photo']))
-                        $object['photo'] = $this->get_attachment($object['_msguid'], $att['id'], $object['_mailbox']);
+                        $object['photo'] = $this->get_attachment($copyfrom, $att['id'], $object['_mailbox']);
                     unset($object['_attachments'][$key]);
                 }
             }
@@ -1010,7 +1011,7 @@ class kolab_storage_folder extends kolab_storage_folder_api
         foreach ((array)$object['_attachments'] as $key => $att) {
             if (empty($att['content']) && !empty($att['id'])) {
                 // @TODO: use IMAP CATENATE to skip attachment fetch+push operation
-                $msguid = !empty($object['_msguid']) ? $object['_msguid'] : $object['uid'];
+                $msguid = $object['_copyfrom'] ?: ($object['_msguid'] ?: $object['uid']);
                 if ($is_file) {
                     $att['path'] = tempnam($temp_dir, 'rcmAttmnt');
                     if (($fp = fopen($att['path'], 'w')) && $this->get_attachment($msguid, $att['id'], $object['_mailbox'], false, $fp, true)) {


commit 5e176baa0832da051f50ef9740fda7c22cb4702f
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Feb 19 18:09:12 2015 +0100

    Pass a list of updated attendess to the backend driver on RSVP reply from calendar view

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index fc4bbff..f5c2c04 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -978,7 +978,8 @@ class calendar extends rcube_plugin
 
       case "rsvp":
         $itip_sending  = $this->rc->config->get('calendar_itip_send_option', $this->defaults['calendar_itip_send_option']);
-        $status        = rcube_utils::get_input_value('status', rcube_utils::INPUT_GPC);
+        $status        = rcube_utils::get_input_value('status', rcube_utils::INPUT_POST);
+        $attendees     = rcube_utils::get_input_value('attendees', rcube_utils::INPUT_POST);
         $reply_comment = $event['comment'];
 
         $this->write_preprocess($event, 'edit');
@@ -990,7 +991,7 @@ class calendar extends rcube_plugin
         // send invitation to delegatee + add it as attendee
         if ($status == 'delegated' && $event['to']) {
           $itip = $this->load_itip();
-          if ($itip->delegate_to($ev, $event['to'], (bool)$event['rsvp'])) {
+          if ($itip->delegate_to($ev, $event['to'], (bool)$event['rsvp'], $attendees)) {
             $this->rc->output->show_message('calendar.itipsendsuccess', 'confirmation');
             $noreply = false;
           }
@@ -998,7 +999,12 @@ class calendar extends rcube_plugin
 
         $event = $ev;
 
-        if ($success = $this->driver->edit_rsvp($event, $status)) {
+        // compose a list of attendees affected by this change
+        $updated_attendees = array_filter(array_map(function($j) use ($ev) {
+          return $ev['attendees'][$j];
+        }, $attendees));
+
+        if ($success = $this->driver->edit_rsvp($event, $status, $updated_attendees)) {
           $noreply = rcube_utils::get_input_value('noreply', rcube_utils::INPUT_GPC);
           $noreply = intval($noreply) || $status == 'needs-action' || $itip_sending === 0;
           $reload  = $event['calendar'] != $ev['calendar'] || $event['recurrence'] ? 2 : 1;
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index c9334ec..e7f55cd 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -2411,6 +2411,7 @@ function rcube_calendar_ui(settings)
         }
 
         // update attendee status
+        attendees = [];
         for (var data, i=0; i < me.selected_event.attendees.length; i++) {
           data = me.selected_event.attendees[i];
           if (settings.identity.emails.indexOf(';'+String(data.email).toLowerCase()) >= 0) {
@@ -2419,6 +2420,7 @@ function rcube_calendar_ui(settings)
 
             if (data.status == 'DELEGATED') {
               data['delegated-to'] = delegate.to;
+              data.rsvp = delegate.rsvp
             }
             else {
               if (data['delegated-to']) {
@@ -2427,6 +2429,8 @@ function rcube_calendar_ui(settings)
                   data.role = 'REQ-PARTICIPANT';
               }
             }
+
+            attendees.push(i)
           }
 
           // set free_busy status to transparent if declined (#4425)
@@ -2459,11 +2463,11 @@ function rcube_calendar_ui(settings)
           });
         }
         else if (settings.invitation_calendars) {
-          update_event('rsvp', submit_data, { status:response, noreply:noreply });
+          update_event('rsvp', submit_data, { status:response, noreply:noreply, attendees:attendees });
         }
         else {
           me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
-          rcmail.http_post('event', { action:'rsvp', e:submit_data, status:response, noreply:noreply });
+          rcmail.http_post('event', { action:'rsvp', e:submit_data, status:response, attendees:attendees, noreply:noreply });
         }
 
         event_show_dialog(me.selected_event);
diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php
index aecd2e1..659f4a0 100644
--- a/plugins/calendar/drivers/calendar_driver.php
+++ b/plugins/calendar/drivers/calendar_driver.php
@@ -197,9 +197,10 @@ abstract class calendar_driver
    *
    * @param array  Hash array with event properties
    * @param string New participant status
+   * @param array  List of hash arrays with updated attendees
    * @return boolean True on success, False on error
    */
-  public function edit_rsvp(&$event, $status)
+  public function edit_rsvp(&$event, $status, $attendees)
   {
     return $this->edit_event($event);
   }
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 5e6f3b3..48dbafd 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -619,9 +619,9 @@ class kolab_driver extends calendar_driver
    * @param string New participant status
    * @return boolean True on success, False on error
    */
-  public function edit_rsvp(&$event, $status)
+  public function edit_rsvp(&$event, $status, $attendees)
   {
-    if (($ret = $this->update_event($event)) && $this->rc->config->get('kolab_invitation_calendars')) {
+    if (($ret = $this->update_attendees($event, $attendees)) && $this->rc->config->get('kolab_invitation_calendars')) {
       // re-assign to the according (virtual) calendar
       if (strtoupper($status) == 'DECLINED')
         $event['calendar'] = self::INVITATIONS_CALENDAR_DECLINED;
diff --git a/plugins/libcalendaring/lib/libcalendaring_itip.php b/plugins/libcalendaring/lib/libcalendaring_itip.php
index e7de5c8..41148c6 100644
--- a/plugins/libcalendaring/lib/libcalendaring_itip.php
+++ b/plugins/libcalendaring/lib/libcalendaring_itip.php
@@ -297,9 +297,10 @@ class libcalendaring_itip
      * @param array Event object to delegate
      * @param mixed Delegatee as string or hash array with keys 'name' and 'mailto'
      * @param boolean The delegator's RSVP flag
+     * @param array List with indexes of new/updated attendees
      * @return boolean True on success, False on failure
      */
-    public function delegate_to(&$event, $delegate, $rsvp = false)
+    public function delegate_to(&$event, $delegate, $rsvp = false, &$attendees = array())
     {
         if (is_string($delegate)) {
             $delegates = rcube_mime::decode_address_list($delegate, 1, false);
@@ -345,6 +346,8 @@ class libcalendaring_itip
         $delegate_attendee['delegated-from'] = $me['email'];
         $event['attendees'][$delegate_index] = $delegate_attendee;
 
+        $attendees[] = $delegate_index;
+
         $this->set_sender_email($me['email']);
         return $this->send_itip_message($event, 'REQUEST', $delegate_attendee, 'itipsubjectdelegatedto', 'itipmailbodydelegatedto');
     }




More information about the commits mailing list